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

package ucar.util.prefs;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import ucar.nc2.constants.CDM;
import ucar.nc2.util.Indent;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
import java.nio.file.*;
import java.util.Stack;

/**
 *  This implements an XML-based backing store for PreferencesExt.
 *  XMLStores can be chained together to provide independent collections of
 * "stored defaults", eg store1 -> store2 -> store3. In this case, a call to getXXX( key)
 * first looks in store1, and if not found, then in store2, etc. A call to
 * putXXX(key, value) first checks to see if the (key, value) pair already exists in any of
 * the chained stores; if not, then it is added to store1.
 * Normally, only store1 would be changed and saved.
 *
 *
 A chain of stored defaults might look like:
 *

 * try {
 *   XMLStore store3 = XMLStore.createFromResource("/auxdata/system.xml", null);
 *   XMLStore store2 = XMLStore.createFromFile("/usr/local/metapps/GDV/site.xml", store3);
 *   XMLStore store1 = XMLStore.createFromFile("/home/username/.GDV/user.xml", store2);
 *   PreferencesExt prefs = store1.getPreferences();
 * } catch (IOException e) {
 *   System.out.println("XMLStore Creation failed "+e);
 * }
 *

 *
 * If you plan to call Preferences.userRoot(), you must explicilty set it, eg:
 *

 *   PreferencesExt.setUserRoot( prefs);
 *

 *
 *
 Before exiting the application, in order to save changes, you must call:
 *

 * try {
 *   store1.save();
 * } catch (IOException e) {
 *   System.out.println("XMLStore Save failed "+e);
 * }
 *

 *
 *
 * @see ucar.util.prefs.PreferencesExt
 * @see java.util.prefs.Preferences
 * @author John Caron
 */

public class XMLStore {

  /**
   * Create an XMLStore reading from the specified filename.
   *
   * @param fileName The XMLStore is stored in this files.
   * @param storedDefaults This contains the "stored defaults", or null if none.
   * @return new XMLStore object
   * @throws java.io.IOException on error
   */
  static public XMLStore createFromFile(String fileName, XMLStore storedDefaults) throws java.io.IOException {
    File prefsFile = new File(fileName);

    // open file if it exists
    InputStream primIS = null, objIS = null;
    if (prefsFile.exists() && prefsFile.length() > 0) {
      primIS = new BufferedInputStream(new FileInputStream( prefsFile));
      objIS = new BufferedInputStream(new FileInputStream( prefsFile));
    }

    if (debugWhichStore) System.out.println("XMLStore read from file "+fileName);
    XMLStore store = new XMLStore( primIS, objIS, storedDefaults);
    store.prefsFile = prefsFile;
    return store;
  }

  /**
   * Create an XMLStore reading from an input stream. Because of some peculiariteis, you must open the
   *  input stream wtice, and pass both in.
   *
   * @param is1 the first copy of the input stream.
   * @param is2 the second copy of the input stream.
   * @param storedDefaults This contains the "stored defaults", or null if none.
   * @return new XMLStore object
   * @throws java.io.IOException on error
   */
  static public XMLStore createFromInputStream(InputStream is1, InputStream is2, XMLStore storedDefaults) throws java.io.IOException {
    if (debugWhichStore) System.out.println("XMLStore read from input stream "+is1);
    return new XMLStore( is1, is2, storedDefaults);
  }

  /**
   * Create a read-only XMLStore reading from the specified resource, opened as a Resource stream using the
   * XMLStore ClassLoader. This allows you to find files that are in jar files on
   * the application CLASSPATH.
   *
   * @param resourceName The XMLStore is stored in this resource. By convention it has .xml suffix.
   * @param storedDefaults This contains the "stored defaults", or null if none.
   * @throws java.io.IOException if Resource not found or error reading it
   * @return new XMLStore object
   */
  static public XMLStore createFromResource(String resourceName, XMLStore storedDefaults)
         throws java.io.IOException {

    // open files if exist
    Class c = XMLStore.class;
    InputStream primIS = c.getResourceAsStream(resourceName);
    InputStream objIS = c.getResourceAsStream(resourceName);

    // debug
//    InputStream debugIS = c.getResourceAsStream(fileName);
  //  System.out.println("Resource stream= "+fileName);
    //thredds.util.IO.copy(debugIS, System.out);

    if (primIS == null) {
      //System.out.println("classLoader="+new XMLStore().getClass().getClassLoader());
      throw new java.io.IOException("XMLStore.createFromResource cant find <"+resourceName+">");
    }

    if (debugWhichStore) System.out.println("XMLStore read from resource "+resourceName);
    return new XMLStore( primIS, objIS, storedDefaults);
  }

  private static boolean debugConvert = false, debugWhichStore = false;
  private static boolean debugWriteNested = false, debugWriteBean = false;

  private File prefsFile = null;
  private PreferencesExt rootPrefs = new PreferencesExt(null, ""); // root node
  private boolean showDecoderExceptions = true; // debugging

  public XMLStore() {}

  /**
   * Constructor. Needs two copies of the same input stream, one for our parser and one for XMLDecoder.
   * @param primIS: store input stream. may be null.
   * @param objIS: store input stream. may be null only if primIS is null.
   * @param storedDefaults: chain to this one.
   * @throws IOException on errpr
   */
  private XMLStore(InputStream primIS, InputStream objIS, XMLStore storedDefaults) throws java.io.IOException {

    // read file if it exists
    if (null != primIS) {
      InputStream convertIS = convert2XmlDecoder(objIS);
      objIS.close();

      // get a SAX parser from JAXP layer
      SAXParserFactory factory = SAXParserFactory.newInstance();
      try {
        SAXParser saxParser = factory.newSAXParser();
        MySaxHandler handler = new MySaxHandler( convertIS);

        // the work is done here
        saxParser.parse(primIS, handler);

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

      } catch (SAXException se) {
        System.out.println("SAXException = "+se.getMessage());
        se.printStackTrace();

        Exception see = se.getException();
        if (see != null) {
          System.out.println("from = "+see.getMessage());
          see.printStackTrace();
        }

      }

        primIS.close();
      convertIS.close();
    }

    // chain
    if (storedDefaults != null)
      rootPrefs.setStoredDefaults( storedDefaults.getPreferences());
  }

  private XMLDecoder openBeanDecoder( InputStream objIS) {
    // filter stream for XMLDecoder
      XMLDecoder beanDecoder = new XMLDecoder( objIS, null, new ExceptionListener() {
        public void exceptionThrown(Exception e) {
          if (showDecoderExceptions)
            System.out.println("***XMLStore.read() got Exception= "+e.getClass().getName()+" "+e.getMessage());
          e.printStackTrace();
        }
      });

      //System.out.println("openBeanDecoder at "+objIS);
      return beanDecoder;
  }

  private InputStream convert2XmlDecoder(InputStream is) throws IOException {
    org.jdom2.Document doc;
    try {
      SAXBuilder builder = new SAXBuilder();
      doc = builder.build(is);
    } catch (JDOMException e) {
      throw new IOException(e.getMessage());
    } finally {
      if (is != null) is.close();
    }

    if (debugConvert) {
      XMLOutputter xmlOut = new XMLOutputter();
      System.out.println("***XMLStore.convert2XmlDecoder = \n" + xmlOut.outputString(doc) + "\n*******");
    }

    Element root = doc.getRootElement();
    org.jdom2.Document convertDoc = new Document();
    Element convertRoot = new Element("java");
    convertRoot.setAttribute("version", "1.4.1_01");
    convertRoot.setAttribute("class", "java.beans.XMLDecoder");
    convertDoc.setRootElement(convertRoot);
    add(root, convertRoot);

    XMLOutputter xmlOut = new XMLOutputter();
    xmlOut.setFormat(Format.getPrettyFormat());
    if (debugConvert) {
      System.out.printf("%n************************************%n");
      System.out.println("***Convert2Version2 converted = \n" + xmlOut.outputString(convertDoc) + "\n*******");
    }

    return new StringBufferInputStream(xmlOut.outputString(convertDoc));
  }

  private void add(Element elem, Element parent) {
    if (elem.getName().equals("object")) {
      parent.addContent( elem.clone());
      return;
    }

    for (Object child : elem.getChildren()) {
      add((Element) child, parent);
    }
  }

  /**
   * Convenience routine for creating an XMLStore file in a standard place.
   *
   *
 Initialize:
   *

   *
 $user.home = System.getProperty("user.home"), if not exist, use "."
   *
 create directory "$(user_home)/appName/" , if not exist, create it
   *
 return  "$(user_home)/appName/storeName" for use in createFromFile()
   *

   * @param appName application name
   * @param storeName store name
   * @return  standard file name
   */
  static public String makeStandardFilename(String appName, String storeName) {
      // the directory
    String userHome = null;
    try {
      userHome = System.getProperty("user.home");
    } catch (Exception e) {
      System.out.println( "XMLStore.makeStandardFilename: error System.getProperty(user.home) "+e);
    }
    if (null == userHome) userHome = ".";

    String dirFilename = userHome+"/"+appName;
    File f = new File(dirFilename);
    if (!f.exists()) {
      boolean ok = f.mkdirs(); // now ready for file creation in writeXML
      if (!ok)
        System.out.println("Error creating directories: " + f.getAbsolutePath());
    }
    return dirFilename +"/"+ storeName;
  }

  /**
   * Get the root Preferences node. All manipulation is done through it.
   */
  public PreferencesExt getPreferences() { return rootPrefs; }

  /////////////////////////////////////////////////
  // reading

  // SAX callback handler
  private class MySaxHandler extends org.xml.sax.helpers.DefaultHandler {
    private boolean debug = false, debugDetail = false;
    private InputStream objIS;
    private XMLDecoder beanDecoder = null; // handles <beanObject> - arbitrary beans

    MySaxHandler (InputStream objIS) throws IOException {
      super();
      this.objIS = objIS;
    }

    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
      if (qName.equalsIgnoreCase("root"))
        startRoot(attributes);
      else if (qName.equalsIgnoreCase("map"))
        startMap(attributes);
      else if (qName.equalsIgnoreCase("node"))
        startNode(attributes);
      else if (qName.equalsIgnoreCase("entry"))
        startEntry(attributes);
      else if (qName.equalsIgnoreCase("bean"))
        startBean(attributes);
      else if (qName.equalsIgnoreCase("beanCollection"))
        startBeanCollection(attributes);
      else if (qName.equalsIgnoreCase("beanObject"))
        startBeanObject(attributes);
      else if (debugDetail)
        System.out.println(" unprocessed startElement = " + qName);
    }

    public void endElement(String uri, String localName, String qName) throws SAXException {
      if (qName.equalsIgnoreCase("node"))
        endNode();
      if (qName.equalsIgnoreCase("beanCollection"))
        endBeanCollection();
      else if (debugDetail)
        System.out.println(" unprocessed endElement = " + qName);
    }

    private Bean.Collection currentBeanCollection = null;
    private Stack<PreferencesExt> stack;
    private PreferencesExt current;
    private void startRoot( Attributes attributes) {
      if (debugDetail) System.out.println(" startRoot ");
      stack = new Stack<>();
      current = rootPrefs;
     }
    private void startMap( Attributes attributes) {
      if (debugDetail) System.out.println(" startMap ");
     }
    private void startNode( Attributes attributes) {
      String name = attributes.getValue("name");
      if (debug) System.out.println(" node = "+name);
      if (name.equals("dumpPane") && debug)
        System.out.println("");
      stack.push( current);
      current = (PreferencesExt) current.node(name); // create new node
     }
    private void startEntry( Attributes attributes) {
      String key = attributes.getValue("key");
      String values = attributes.getValue("value"); // values or value ??
      if (debug) System.out.println(" entry = "+key+" "+values);
      current.put(key, values);
     }
    private void startBean( Attributes attributes) {
      String key = attributes.getValue("key");
      try {
        if (currentBeanCollection != null) {
          Object value = currentBeanCollection.readProperties( attributes);
          if (debug) System.out.println(" bean(collection) = "+key+" value= "+value);
        } else {
          Object value = new Bean( attributes);
          if (debug) System.out.println(" bean = "+key+" value= "+value);
          current.putObject(key, value);
        }
      } catch (Exception e) {
        e.printStackTrace();
      } // ??
    }
    private void startBeanCollection( Attributes attributes) {
      String key = attributes.getValue("key");
      try {
        currentBeanCollection = new Bean.Collection( attributes);
        if (debug) System.out.println(" beanCollection = "+key);
        current.putObject(key, currentBeanCollection);
      } catch (ClassNotFoundException e) {} // ??
    }
    private void startBeanObject( Attributes attributes) {
      if (beanDecoder == null)
        beanDecoder = openBeanDecoder( objIS);

      String key = attributes.getValue("key");
      try {
        if (debug) System.out.print(" beanObject = "+key+" ");
        Object value = beanDecoder.readObject(); // read from filtered stream
        if (debug) System.out.println(" value= "+value);
        current.putObject(key, value);
      } catch (Exception e) {
        System.out.println("#ERROR beanDecoder; beanObject key = "+key);
        e.printStackTrace();
      }
    }

    private void endBeanCollection( ) { currentBeanCollection = null; }
    private void endNode( ) { current = stack.pop(); }
  }

  String findAttribute(Attributes atts, String what) {
    for (int i=0; i< atts.getLength(); i++) {
      if (atts.getLocalName(i).equals(what))
        return atts.getValue(i);
    }
    return null;
  }

  /////////////////////////////////////////////////
  // writing

  /**
   * Save the current state of the Preferences tree to disk, using the
   * original filename. The XMLStore must have been constructed from a
   * writeable XML file.
   * @throws UnsupportedOperationException: if XMLStore was created from createFromResource.
   * @throws java.io.IOException on read error
   */
  public void save() throws java.io.IOException {
    if (prefsFile == null)
      throw new UnsupportedOperationException("XMLStore is read-only");

      // get temporary file to write to
    File prefTemp;
    String parentFilename = prefsFile.getParent();
    if (parentFilename == null) {
      prefTemp = File.createTempFile("pref", ".xml");
    } else {
      File parentFile = new File(parentFilename);
      prefTemp = File.createTempFile("pref", ".xml", parentFile);
    }
    prefTemp.deleteOnExit();

    // save to the temp file
    FileOutputStream fos = new FileOutputStream( prefTemp, false);
    save( fos);
    fos.close();

    // success - rename files
    Path xmlBackup = Paths.get(prefsFile.getAbsolutePath() + ".bak");
    Path prefsPath = prefsFile.toPath();
    if (Files.exists(prefsPath))
        Files.move(prefsPath, xmlBackup, StandardCopyOption.REPLACE_EXISTING);
    Files.move(prefTemp.toPath(), prefsFile.toPath(),
            StandardCopyOption.REPLACE_EXISTING);
  }

  /**
   * Save the current state of the Preferences tree to the given OutputStream.
   */
  public void save(OutputStream out) throws java.io.IOException {
    outputExceptionMessage = null;

    // the OutputMunger strips off the XMLEncoder header
    OutputMunger bos = new OutputMunger( out);
    PrintWriter pw = new PrintWriter( new OutputStreamWriter(bos,
            CDM.utf8Charset));

    XMLEncoder beanEncoder = new XMLEncoder( bos);
    beanEncoder.setExceptionListener(new ExceptionListener() {
      public void exceptionThrown(Exception exception) {
        System.out.println("XMLStore.save() got Exception: abort saving the preferences!");
        exception.printStackTrace();
        outputExceptionMessage = exception.getMessage();
      }
    });

    pw.printf("<?xml version='1.0' encoding='UTF-8'?>%n");
    pw.printf("<preferences EXTERNAL_XML_VERSION='1.0'>%n");
    if (!rootPrefs.isUserNode())
      pw.printf("  <root type='system'>%n");
    else
      pw.printf("  <root type='user'>%n");

    Indent indent = new Indent(2);
    indent.incr();
    writeXmlNode(bos, pw, rootPrefs, beanEncoder, indent);
    if (outputExceptionMessage != null)
      throw new IOException(outputExceptionMessage);

    pw.printf("  </root>%n");
    pw.printf("</preferences>%n");
    pw.flush();
  }
  private String outputExceptionMessage;

  private void writeXmlNode( OutputMunger bos, PrintWriter out, PreferencesExt prefs, XMLEncoder beanEncoder, Indent indent) throws IOException {
    indent.incr();

    if (debugWriteNested) System.out.println(" writeXmlNode "+prefs);
    if (debugWriteBean) {
      ClassLoader l = Thread.currentThread().getContextClassLoader();
      System.out.println("  ClassLoader "+l.getClass().getName());
    }

    try {
      String[] keys = prefs.keysNoDefaults();
      if (keys.length == 0) {
        out.printf("%s<map/>%n", indent);
      } else {
        out.printf("%s<map>%n", indent);
        for (String key: keys) {
          Object value =  prefs.getObjectNoDefaults(key);
          // LOOK! test if in stored defaults ??

          if (value instanceof String) {
            if (debugWriteNested) System.out.println("  write entry "+key+" "+value);
            out.printf("%s  <entry key='%s' value='%s' />%n", indent, key, quote((String) value));

          } else if (value instanceof Bean.Collection) {
            Bean.Collection bean = (Bean.Collection) value;
            if (debugWriteNested) System.out.println("  write bean collection "+key);

            if (bean.getCollection().isEmpty()) // skip empty ??
              continue;

            out.printf("%s  <beanCollection key='%s' class='%s' >%n", indent, key, bean.getBeanClass().getName());

            for (Object o : bean.getCollection()) {
              out.printf("%s    <bean ", indent);
              bean.writeProperties(out, o);
              out.println("/>");
            }
            out.printf("%s  </beanCollection>", indent);

          } else if (value instanceof Bean) {
            Bean bean = (Bean) value;
            if (debugWriteNested) System.out.println("  write bean "+key+" "+value);
            out.printf("%s  <bean key='%s' class='%s' ", indent, key, bean.getBeanClass().getName());
            bean.writeProperties( out);
            out.printf("/>%n");

          } else { // not a String or Bean
            out.printf("%s  <beanObject key='%s' >%n", indent, key);
            out.flush();
            bos.enterBeanStream();
            try {
              if (debugWriteNested || debugWriteBean) System.out.println("  write beanObject "+key+" "+value+" "+value.getClass().getName());
              beanEncoder.writeObject( value);
              if (debugWriteBean) System.out.println("  write bean done ");
            } catch (Exception e) {
              System.out.println("Exception beanEncoder: "+e);
              e.printStackTrace();
              throw new IOException(e.getMessage());
            }
            beanEncoder.flush();
            bos.exitBeanStream();
            out.printf("%s  </beanObject>%n", indent);
          }
        }
        out.printf("%s</map>%n", indent);
      }

      for (String kidName: prefs.childrenNames()) {
        PreferencesExt pkid = (PreferencesExt) prefs.node(kidName);
        out.printf("%s<node name='%s' >%n", indent, pkid.name());
        writeXmlNode(bos, out, pkid, beanEncoder, indent);
        out.printf("%s</node>%n", indent);
      }

    } catch (java.util.prefs.BackingStoreException e) {
      e.printStackTrace();
      throw new IOException(e.getMessage());
    }

    indent.decr();
  }

  static private char[] replaceChar = {'&', '<', '>', '\'', '"', '\r', '\n'};
  static private String[] replaceWith = {"&", "<", ">", "'", """, "
", "
" };

  static String quote(String x) {
    // common case no replacement
    boolean ok = true;
    for (char c: replaceChar) {
      int pos = x.indexOf(c);
      ok &= (pos < 0);
    }
    if (ok) return x;

    // gotta do it
    StringBuffer result = new StringBuffer(x);
    for (int i=0; i<replaceChar.length; i++) {
      int pos = x.indexOf(replaceChar[i]);
      if (pos >= 0)
        replace( result, replaceChar[i], replaceWith[i]);
    }

    return result.toString();
  }

  static private void replace(StringBuffer sb, char out, String in) {
    for (int i = 0; i < sb.length(); i++) {
      if (sb.charAt(i) == out) {
        sb.replace(i, i+1, in);
        i += in.length();
      }
    }
  }

  //private final int DIE = 0; // 97;
  private static class OutputMunger extends java.io.BufferedOutputStream {
    boolean done = false;
    boolean bean = false;
    int countNL = 0;

    OutputMunger(OutputStream out) { super(out, 1024); }

    void enterBeanStream() { bean = true; }

    void exitBeanStream() { bean = false; }

    public void write(int b) throws IOException {
      if (done || !bean) super.write(b);
      else {
        if (b == '\n') countNL++;
        if (countNL == 2) done = true; // skip 2 lines
      }
    }

    public void write(byte b[], int off, int len) throws IOException {
      if (done || !bean) super.write(b, off, len);
      else {
        for (int i=0; i<len; i++)
          write(b[off+i]);
      }
    }

  }

}


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

package dap4.servlet;

import dap4.core.ce.CEConstraint;
import dap4.core.data.ChecksumMode;
import dap4.core.data.DSP;
import dap4.core.data.DataCursor;
import dap4.core.dmr.*;
import dap4.core.util.DapException;
import dap4.core.util.Index;
import dap4.core.util.Odometer;
import dap4.core.util.Slice;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.List;

/**
 * Given a DSP, serialize
 * possibly constrained data.
 */

public class DapSerializer
{
    //////////////////////////////////////////////////
    // Instance variables

    protected OutputStream stream = null;
    protected SerialWriter writer = null;
    protected DSP dsp = null;
    protected CEConstraint ce = null;
    protected ByteOrder order = null;
    protected ChecksumMode checksummode = null;

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

    public DapSerializer()
    {
    }

    /**
     * Primary constructor
     *
     * @param dsp        The DSP to write
     * @param constraint Any applicable constraint
     * @param stream     Write to this stream
     * @param order      The byte order to use
     */
    public DapSerializer(DSP dsp, CEConstraint constraint,
                         OutputStream stream, ByteOrder order, ChecksumMode mode)
            throws IOException
    {
        this.dsp = dsp;
        this.order = order;
        this.checksummode = mode;
        this.stream = stream;
        this.ce = constraint;
    }

    public void
    write(DapDataset dmr)
            throws IOException
    {
        writer = new SerialWriter(this.stream, this.order, this.checksummode);
        writer.flush(); // If stream is ChunkWriter, then dump DMR
        // Iterate over the top-level variables in the constraint
        for(DapVariable var : dmr.getTopVariables()) {
            if(!this.ce.references(var))
                continue;
            DataCursor vardata = this.dsp.getVariableData(var);
            if(vardata == null)
                throw new dap4.core.util.DapException("DapSerializer: cannot find  Variable data " + var.getFQN());
            writeVariable(vardata, writer);
        }
    }

    //////////////////////////////////////////////////
    // Recursive variable writer

    /**
     * @param data - cursor referencing what to write
     * @param dst  - where to write
     * @throws IOException
     */
    protected void
    writeVariable(DataCursor data, SerialWriter dst)
            throws IOException
    {
        DapVariable template = (DapVariable) data.getTemplate();
        dst.startVariable();
        switch (data.getScheme()) {
        case ATOMIC:
            writeAtomicVariable(data, dst);
            break;
        case STRUCTARRAY:
            //case STRUCTURE:
            writeStructure(data, dst);
            break;
        case SEQARRAY:
            //case SEQUENCE:
            writeSequence(data, dst);
            break;
        default:
            assert false : "Unexpected variable type: " + data.toString();
        }
        dst.endVariable();
    }

    /**
     * Write out an atomic variable.
     *
     * @param data
     * @param dst
     * @throws dap4.core.util.DapException
     */
    protected void
    writeAtomicVariable(DataCursor data, SerialWriter dst)
            throws IOException
    {
        DapVariable template = (DapVariable) data.getTemplate();
        assert (this.ce.references(template));
        DapType basetype = template.getBaseType();
        // get the slices from constraint
        List<Slice> slices = ce.getConstrainedSlices(template);
        if(slices == null)
            throw new DapException("Unknown variable: " + template.getFQN());
        Object values = data.read(slices);
        dst.writeAtomicArray(basetype, values);
    }

    /**
     * Write out a scalar or array structure instance
     *
     * @param data
     * @param dst  - where to write
     * @throws dap4.core.util.DapException
     */

    protected void
    writeStructure(DataCursor data, SerialWriter dst)
            throws IOException
    {
        DapVariable template = (DapVariable) data.getTemplate();
        DapStructure ds = (DapStructure) template.getBaseType();
        assert (this.ce.references(template));
        List<Slice> slices = ce.getConstrainedSlices(template);
        Odometer odom = Odometer.factory(slices);
        while(odom.hasNext()) {
            Index index = odom.next();
            DataCursor[] instance = (DataCursor[]) data.read(index);
            writeStructure1(instance[0], dst);
        }
    }

    /**
     * Write out a single structure instance
     *
     * @param instance
     * @param dst      - where to write
     * @throws dap4.core.util.DapException
     */

    protected void
    writeStructure1(DataCursor instance, SerialWriter dst)
            throws IOException
    {
        assert instance.getScheme() == DataCursor.Scheme.STRUCTURE;
        DapVariable template = (DapVariable) instance.getTemplate();
        assert (this.ce.references(template));
        DapStructure ds = (DapStructure) template.getBaseType();

        List<DapVariable> fields = ds.getFields();
        for(int i = 0; i < fields.size(); i++) {
            DapVariable field = fields.get(i);
            if(!this.ce.references(field)) continue; // not in the view
            DataCursor df = (DataCursor) instance.readField(i);
            writeVariable(df, dst);
        }
    }

    /**
     * Write out a single or array sequence instance
     *
     * @param data
     * @param dst  - where to write
     * @throws dap4.core.util.DapException
     */

    protected void
    writeSequence(DataCursor data, SerialWriter dst)
            throws IOException
    {
        DapVariable template = (DapVariable) data.getTemplate();
        DapSequence ds = (DapSequence) template.getBaseType();
        assert (this.ce.references(template));
        List<Slice> slices = ce.getConstrainedSlices(template);
        Odometer odom = Odometer.factory(slices);
        if(false) while(odom.hasNext()) {
            Index index = odom.next();
            DataCursor[] instance = (DataCursor[]) data.read(index);
            writeSequence1(instance[0], dst);
        }
        else {
            DataCursor[] instances = (DataCursor[]) data.read(slices);
            for(int i = 0; i < instances.length; i++) {
                writeSequence1(instances[i], dst);
            }

        }
    }

    /**
     * Write out a single Sequence of records
     * (Eventually use any filter in the DapVariable)
     *
     * @param instance the sequence instance
     * @param dst      write target
     * @throws dap4.core.util.DapException
     */

    protected void
    writeSequence1(DataCursor instance, SerialWriter dst)
            throws IOException
    {
        DapVariable template = (DapVariable) instance.getTemplate();
        DapSequence seq = (DapSequence) template.getBaseType();
        assert (this.ce.references(template));
        long nrecs = instance.getRecordCount();
        dst.writeCount(nrecs);
        for(long i = 0; i < nrecs; i++) {
            DataCursor record = instance.readRecord(i);
            writeRecord(record, dst);
        }
    }

    /**
     * Write out a single Record instance.
     *
     * @param record the record data cursor
     * @param dst    to which to write
     * @throws dap4.core.util.DapException
     */

    protected void
    writeRecord(DataCursor record, SerialWriter dst)
            throws IOException
    {
        DapVariable template = (DapVariable) record.getTemplate();
        DapSequence seq = (DapSequence) template.getBaseType();
        List<DapVariable> fields = seq.getFields();
        for(int i = 0; i < fields.size(); i++) {
            DapVariable field = fields.get(i);
            if(!this.ce.references(field)) continue; // not in the view
            DataCursor df = (DataCursor) record.readField(i);
            writeVariable(df, dst);
        }
    }

}


/////////////////////////////////////////////////////////////////////////////
// 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.*;
import java.util.Enumeration;
import java.util.Vector;

import opendap.dap.*;

/**
 */
public class asciiSeq extends DSequence implements toASCII {

    private static boolean _Debug = false;

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

    /**
     * Constructs a new asciiSeq with name n.
     *
     * @param n the name of the variable.
     */
    public asciiSeq(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("asciiSeq.toASCII(" + addName + ",'" + rootName + "')  getName(): " + getEncodedName());
        //System.out.println("this: " + this + " Has "+allValues.size() + " elements.");

        if (rootName != null)
            rootName += "." + getEncodedName();
        else
            rootName = getEncodedName();

        pw.print(toASCIIFlatName(rootName));

/*
        for(Enumeration e1 = allValues.elements(); e1.hasMoreElements(); ) {
            // get next instance vector
            Vector v = (Vector)e1.nextElement();
            for(Enumeration e2 = v.elements(); e2.hasMoreElements(); ) {
                // get next instance variable
                BaseType bt = (BaseType)e2.nextElement();

        pw.print(bt.toASCIIFlatName(rootName)+",");
            }
        break;
        }
*/

        pw.println("");

        int i = 0;
        for (Enumeration e1 = allValues.elements(); e1.hasMoreElements();) {

            int j = 0;
            // get next instance vector
            Vector v = (Vector) e1.nextElement();
            for (Enumeration e2 = v.elements(); e2.hasMoreElements();) {
                // get next instance variable
                toASCII ta = (toASCII) e2.nextElement();

                if (j > 0) pw.print(", ");
                ta.toASCII(pw, false, rootName, false);
                j++;
            }
            pw.println("");
        }

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

    }

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

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

    }

    public String toASCIIFlatName(String rootName) {

        StringBuilder s = new StringBuilder();
        boolean firstPass = true;
        for (Enumeration e1 = allValues.elements(); e1.hasMoreElements();) {
            // get next instance vector
            Vector v = (Vector) e1.nextElement();
            for (Enumeration e2 = v.elements(); e2.hasMoreElements();) {
                // get next instance variable
                toASCII ta = (toASCII) e2.nextElement();
                if (!firstPass)
                    s.append(", ");
                s.append(ta.toASCIIFlatName(rootName));
                firstPass = false;
            }
            break;
        }
        return s.toString();
    }

}


/*
 * 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.DataType;
import ucar.ma2.Range;
import ucar.ma2.RangeComposite;
import ucar.nc2.Attribute;
import ucar.nc2.AttributeContainer;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.util.Counters;
import ucar.nc2.util.Misc;
import ucar.unidata.util.StringUtil2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Mutable builder object for CoverageCoordAxis
 *
 * @author caron
 */

public class CoverageCoordAxisBuilder {

  public String name;
  public String description;
  public DataType dataType;
  public AxisType axisType;    // ucar.nc2.constants.AxisType ordinal
  public AttributeContainer attributes;
  public CoverageCoordAxis.DependenceType dependenceType;
  public List<String> dependsOn;

  public int ncoords;            // number of coordinates (not values)
  public CoverageCoordAxis.Spacing spacing;
  public double startValue;
  public double endValue;
  public double resolution;
  public CoordAxisReader reader;
  public boolean isSubset;

  public TimeHelper timeHelper; // AxisType = Time, RunTime only
  public String units;

  public double[] values;

  // 1D only
  public Range range; // set when its a subset
  public RangeComposite crange;

  //int minIndex, maxIndex; // closed interval [minIndex, maxIndex] ie minIndex to maxIndex are included, nvalues = max-min+1.
  //public int stride = 1;
  // public boolean isTime2D;

  // 2d only
  public int[] shape;
  public Object userObject;

  public CoverageCoordAxisBuilder() {
  }

  public CoverageCoordAxisBuilder(String name, String units, String description, DataType dataType, AxisType axisType, AttributeContainer atts,
                                  CoverageCoordAxis.DependenceType dependenceType, String dependsOnS, CoverageCoordAxis.Spacing spacing,
                                  int ncoords, double startValue, double endValue, double resolution, double[] values, CoordAxisReader reader) {
    this.name = name;
    this.units = units;
    this.description = description;
    this.dataType = dataType;
    this.axisType = axisType;
    this.attributes = atts;
    this.dependenceType = dependenceType;
    this.setDependsOn(dependsOnS);
    this.spacing = spacing;
    this.ncoords = ncoords;
    this.startValue = startValue;
    this.endValue = endValue;
    this.resolution = resolution;
    this.values = values;
    this.reader = reader;
  }

  CoverageCoordAxisBuilder(CoverageCoordAxis from) {
    this.name = from.name;
    this.units = from.units;
    this.description = from.description;
    this.dataType = from.dataType;
    this.axisType = from.axisType;
    this.attributes = from.attributes;
    this.dependenceType = from.dependenceType;
    this.spacing = from.spacing;
    this.values = from.values;
    this.reader = from.reader; // used only if values == null
    this.dependsOn = from.dependsOn;
    this.startValue = from.startValue;
    this.endValue = from.endValue;
    this.resolution = from.resolution;

    this.ncoords = from.ncoords;
    this.isSubset = from.isSubset;
    this.timeHelper = from.timeHelper;

    if (from instanceof LatLonAxis2D) {
      LatLonAxis2D latlon = (LatLonAxis2D) from;
      this.shape = latlon.getShape();
      this.userObject = latlon.getUserObject();
    }
  }

  public void setIsSubset(boolean isSubset) {
    this.isSubset = isSubset;
  }

  public CoverageCoordAxisBuilder setDependsOn(String dependsOn) {
    if (dependsOn != null && dependsOn.trim().length() > 0) {
      List<String> temp = new ArrayList<>();
      Collections.addAll(temp, StringUtil2.splitString(dependsOn));
      this.dependsOn = Collections.unmodifiableList(temp);
    } else {
      this.dependsOn = Collections.emptyList();
    }
    return this;
  }

  ///////////////////////////////////////////////////////////////////////
  //// this could be moved into grib ncx calculation
  // for point: values are the points, values[npts]
  // for intervals: values are the edges, values[2*npts]: low0, high0, low1, high1

  public void setSpacingFromValues(boolean isInterval) {
    if (isInterval)
      setSpacingFromIntervalValues();
    else
      setSpacingFromPointValues();
  }

  private void setSpacingFromPointValues() {
    assert (values.length == ncoords);

    this.startValue = values[0];
    this.endValue = values[ncoords - 1];
    this.resolution = (ncoords == 1) ? 0.0 : (endValue - startValue) / (ncoords - 1);

    if (ncoords == 1) {
      this.spacing = CoverageCoordAxis.Spacing.regularPoint;
      values = null;
      return;
    }

    this.resolution = (endValue - startValue) / (ncoords - 1);

    Counters.Counter resol = new Counters.Counter("resol");
    for (int i = 0; i < values.length - 1; i++) {
      double diff = values[i + 1] - values[i];
      resol.count(diff);
    }

    Comparable resolMode = resol.getMode();
    if (resolMode != null)
      this.resolution = ((Number) resolMode).doubleValue();

    boolean isRegular = isRegular(resol);
    this.spacing = isRegular ? CoverageCoordAxis.Spacing.regularPoint : CoverageCoordAxis.Spacing.irregularPoint;
    if (isRegular) values = null;
  }

  private void setSpacingFromIntervalValues() {
    assert (values.length == 2 * ncoords);

    this.startValue = values[0];
    this.endValue = values[values.length - 1];
    this.resolution = (endValue - startValue) / ncoords;

    Counters.Counter resol = new Counters.Counter("resol");
    boolean isContiguous = true;
    for (int i = 0; i < values.length - 2; i += 2) {
      double diff = values[i + 2] - values[i];  // difference of consecutive starting interval values // LOOK roundoff
      resol.count(diff);
      if (isContiguous && !Misc.nearlyEquals(values[i+1], values[i+2])) // difference of this ending interval values with next starting value
        isContiguous = false;
    }

    Comparable resolMode = resol.getMode();
    if (resolMode != null) {
      double modeValue = ((Number) resolMode).doubleValue();
      if (modeValue != 0.0)
        this.resolution = modeValue;
    }

    boolean regular = isRegular(resol);

    // not dealing correctly with n == 2 case
    if (ncoords == 2) {
      double diff0 = values[1] - values[0];
      double diff1 = values[3] - values[2];
      regular = Misc.nearlyEquals(diff0, diff1);
    }

    if (regular && isContiguous) {
      this.spacing = CoverageCoordAxis.Spacing.regularInterval;
      this.values = null;

    } else if (isContiguous) {
      this.spacing = CoverageCoordAxis.Spacing.contiguousInterval;
      double[] contValues = new double[ncoords+ 1];
      int count = 0;
      for (int i = 0; i < values.length; i += 2)
        contValues[count++] = values[i]; // starting interval
      contValues[count] = values[values.length - 1]; // ending interval
      this.values = contValues;

    } else {
      this.spacing = CoverageCoordAxis.Spacing.discontiguousInterval;
    }
  }

  private static final double missingTolerence = .05;

  private boolean isRegular(Counters.Counter resol) {
    if (resol.getUnique() == 1) return true; // all same resolution, or n == 1

    Comparable mode = resol.getMode();
    Number modeNumber = (Number) mode;
    if (modeNumber == null || modeNumber.intValue() == 0)
      return false;

    int modeCount = 0;
    int nonModeCount = 0;
    for (Comparable value : resol.getValues()) {
      if (value.compareTo(mode) == 0)
        modeCount = resol.getCount(value);
      else {
        Number valueNumber = (Number) value;
        // non mode must be a multiple of mode - means there are some missing values
        int rem = (valueNumber.intValue() % modeNumber.intValue());
        if (rem != 0)
          return false;
        int multiple = (valueNumber.intValue() / modeNumber.intValue());
        nonModeCount += (multiple - 1) * resol.getCount(value);
      }
    }
    if (modeCount == 0) return true; // cant happen i think

    // only tolerate these many missing values
    double ratio = (nonModeCount / (double) modeCount);
    return ratio < missingTolerence;
  }

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

  CoverageCoordAxisBuilder subset(String dependsOn, CoverageCoordAxis.Spacing spacing, int ncoords, double[] values) {
    assert values != null;
    if (dependsOn != null) {
      this.dependenceType = CoverageCoordAxis.DependenceType.dependent;
      setDependsOn(dependsOn);
    }
    this.spacing = spacing;
    this.ncoords = ncoords;
    this.reader = null;
    this.values = values;
    this.isSubset = true;

    return this;
  }

  CoverageCoordAxisBuilder subset(int ncoords, double startValue, double endValue, double resolution, double[] values) {
    this.ncoords = ncoords;
    this.startValue = startValue;
    this.endValue = endValue;
    this.resolution = resolution;
    this.values = values;
    this.isSubset = true;

    return this;
  }

  /* CoverageCoordAxisBuilder setIndexRange(int minIndex, int maxIndex, int stride) {
    this.minIndex = minIndex;
    this.maxIndex = maxIndex;
    this.stride = stride;
    this.isSubset = true;
    return this;
  } */

  CoverageCoordAxisBuilder setRange(Range r) {
    this.range = r;
    this.isSubset = true;
    return this;
  }

  CoverageCoordAxisBuilder setCompositeRange(RangeComposite cr) {
    this.crange = cr;
    this.isSubset = true;
    return this;
  }

  void setReferenceDate(CalendarDate refDate) {
    this.timeHelper = timeHelper.setReferenceDate(refDate);
    this.units = timeHelper.getUdUnit();
    if (attributes != null) {
      attributes.addAttribute(new Attribute(CDM.UNITS, this.units));
    }
  }

}


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

import ucar.nc2.constants.CDM;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.io.KMPMatch;

import java.io.*;
import java.nio.channels.WritableByteChannel;

/**
 * Sequentially scans a BUFR file, extracts the messages.
 *
 * @author caron
 * @since May 9, 2008
 */
public class MessageScanner {
//  static public final int MAX_MESSAGE_SIZE = 500 * 1000; // GTS allows up to 500 Kb messages (ref?)
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MessageScanner.class);

  static private final KMPMatch matcher = new KMPMatch(
          "BUFR".getBytes(CDM.utf8Charset));

  /**
   * is this a valid BUFR file.
   *
   * @param raf check this file
   * @return true if its a BUFR file
   * @throws IOException on read error
   */
  static public boolean isValidFile(ucar.unidata.io.RandomAccessFile raf) throws IOException {
    raf.seek(0);
    if (!raf.searchForward(matcher, 40 * 1000)) return false; // must find "BUFR" in first 40k
    raf.skipBytes(4);
    BufrIndicatorSection is = new BufrIndicatorSection(raf);
    if (is.getBufrEdition() > 4) return false;
    // if(is.getBufrLength() > MAX_MESSAGE_SIZE) return false;
    return !(is.getBufrLength() > raf.length());
  }

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

  private ucar.unidata.io.RandomAccessFile raf = null;
  private boolean useEmbeddedTables;

  private int countMsgs = 0;
  private int countObs = 0;
  private byte[] header;
  private long startPos = 0;
  private long lastPos = 0;
  private boolean debug = false;

  private EmbeddedTable embedTable = null;

  public MessageScanner(RandomAccessFile raf) throws IOException {
    this(raf, 0, true);
  }

  public MessageScanner(RandomAccessFile raf, long startPos, boolean useEmbeddedTables) throws IOException {
    startPos = (startPos < 30) ? 0 : startPos - 30; // look for the header
    this.raf = raf;
    lastPos = startPos;
    this.useEmbeddedTables = useEmbeddedTables;
    raf.seek(startPos);
    raf.order(RandomAccessFile.BIG_ENDIAN);
  }

  public Message getFirstDataMessage() throws IOException {
    while (hasNext()) {
      Message m = next();
      if (m == null) continue;
      if (m.containsBufrTable()) continue; // not data
      if (m.getNumberDatasets() == 0) continue; // empty
      return m;
    }
    return null;
  }

  public void reset() {
    lastPos = 0;
  }

  public boolean hasNext() throws IOException {
    if (lastPos >= raf.length()) return false;
    raf.seek(lastPos);
    boolean more = raf.searchForward(matcher, -1); // will scan to end for another BUFR header
    if (more) {
      long stop = raf.getFilePointer();
      int sizeHeader = (int) (stop - lastPos);
      if (sizeHeader > 30) sizeHeader = 30;
      header = new byte[sizeHeader];
      startPos = stop-sizeHeader;
      raf.seek(startPos);
      int nRead = raf.read(header);
      if (nRead != header.length) {
          log.warn("Unable to read full BUFR header. Got " + nRead +
                  " but expected " + header.length);
          return false;
      }
    }
    if (debug && countMsgs % 100 == 0) System.out.printf("%d ", countMsgs);
    return more;
  }

  public Message next() {

    try {
      long start = raf.getFilePointer();
      raf.seek(start + 4);

      BufrIndicatorSection is = new BufrIndicatorSection(raf);
      BufrIdentificationSection ids = new BufrIdentificationSection(raf, is);
      BufrDataDescriptionSection dds = new BufrDataDescriptionSection(raf);

      long dataPos = raf.getFilePointer();
      int dataLength = BufrNumbers.uint3(raf);
      BufrDataSection dataSection = new BufrDataSection(dataPos, dataLength);
      lastPos = dataPos + dataLength + 4; // position to the end message plus 1
      //nbytes +=  lastPos - startPos;

      /* length consistency checks
      if (is.getBufrLength() > MAX_MESSAGE_SIZE) {
        log.warn("Illegal length - BUFR message at pos "+start+" header= "+cleanup(header)+" size= "+is.getBufrLength());
        return null;
      } */

      if (is.getBufrEdition() > 4) {
        log.warn("Illegal edition - BUFR message at pos " + start + " header= " + cleanup(header));
        return null;
      }

      if (is.getBufrEdition() < 2) {
        log.warn("Edition "+ is.getBufrEdition()+" is not supported - BUFR message at pos " + start + " header= " +cleanup(header));
        return null;
      }

      // check that end section is correct
      long ending = dataPos + dataLength;
      raf.seek(dataPos + dataLength);
      for (int i = 0; i < 3; i++) {
        if (raf.read() != 55) {
          log.warn("Missing End of BUFR message at pos= {} header= {} file= {}", ending, cleanup(header), raf.getLocation());
          return null;
        }
      }
      // allow off by one : may happen when dataLength rounded to even bytes
      if (raf.read() != 55) {
        raf.seek(dataPos + dataLength-1); // see if byte before is a '7'
        if (raf.read() != 55) {
          log.warn("Missing End of BUFR message at pos= {} header= {} edition=() file= {}", ending, cleanup(header), is.getBufrEdition(), raf.getLocation());
          return null;
        } else {
          log.info("End of BUFR message off-by-one at pos= {} header= {} edition=() file= {}", ending, cleanup(header), is.getBufrEdition(), raf.getLocation());
          lastPos--;
        }
      }

      Message m = new Message(raf, is, ids, dds, dataSection);
      m.setHeader( cleanup(header));
      m.setStartPos( start);

      if (useEmbeddedTables && m.containsBufrTable()) {
        if (embedTable == null) embedTable = new EmbeddedTable(m, raf);
        embedTable.addTable(m);
      } else if (embedTable != null) {
        m.setTableLookup(embedTable.getTableLookup());
      }

      countMsgs++;
      countObs += dds.getNumberDatasets();
      raf.seek(start + is.getBufrLength());
      return m;

    } catch (IOException ioe) {
      log.error("Error reading message at "+lastPos, ioe);
      try {
        lastPos = raf.getFilePointer(); // dont do an infinite loop
      } catch (IOException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
      return null;
    }
  }

  public TableLookup getTableLookup() throws IOException {
    while (hasNext()) {
      next();
    }
    return (embedTable != null) ? embedTable.getTableLookup() : null;
  }

  public byte[] getMessageBytesFromLast(Message m) throws IOException {
    long startPos = m.getStartPos();
    int length = (int) (lastPos - startPos);
    byte[] result = new byte[length];

    raf.seek(startPos);
    raf.readFully(result);
    return result;
  }

  public byte[] getMessageBytes(Message m) throws IOException {
    long startPos = m.getStartPos();
    int length = m.is.getBufrLength();
    byte[] result = new byte[length];

    raf.seek(startPos);
    raf.readFully(result);
    return result;
  }

  public int getTotalObs() {
    return countObs;
  }

  public int getTotalMessages() {
    return countMsgs;
  }

    // the WMO header is in here somewhere when the message comes over the IDD
  private static String cleanup(byte[] h) {
    byte[] bb = new byte[h.length];
    int count = 0;
    for (byte b : h) {
      if (b >= 32 && b < 127)
        bb[count++] = b;
    }
    return new String(bb, 0, count, CDM.utf8Charset);
  }

  public long writeCurrentMessage( WritableByteChannel out) throws IOException {
    long nbytes = lastPos - startPos;
    return  raf.readToByteChannel(out, startPos, nbytes);
  }

}


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

package ucar.nc2.iosp.gempak;

import ucar.unidata.geoloc.LatLonPoint;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.Station;
import ucar.unidata.util.Format;
import ucar.unidata.util.StringUtil2;

/**
 * Class to hold GEMPAK station information
 *
 * @author Don Murray
 */
public class GempakStation implements Station {

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

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

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

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

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

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

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

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

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

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

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

  /**
   * station id
   */
  private String stid = "";

  /**
   * station id2
   */
  private String std2 = "";

  /**
   * station number
   */
  private int stnm = GempakConstants.IMISSD;

  /**
   * state
   */
  private String stat = "";

  /**
   * country
   */
  private String coun = "";

  /**
   * wfo id
   */
  private String swfo = "";

  /**
   * second wfo id
   */
  private String wfo2 = "";

  /**
   * station latitude
   */
  private int slat = GempakConstants.IMISSD;

  /**
   * station longitude
   */
  private int slon = GempakConstants.IMISSD;

  /**
   * station elevation
   */
  private int selv = GempakConstants.IMISSD;

  /**
   * priority
   */
  private int spri;

  /**
   * row or column index
   */
  private int index;

  /**
   * Station description
   */
  private String sdesc = "";

  /**
   * Create a new GEMPAK station
   */
  public GempakStation() {
  }

  /**
   * Return a String representation of this
   *
   * @return a String representation of this
   */
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append(StringUtil2.padRight((stid.trim() + std2.trim()), 8));
    builder.append(" ");
    builder.append(Format.i(stnm, 6));
    builder.append(" ");
    builder.append(StringUtil2.padRight(sdesc, 32));
    builder.append(" ");
    builder.append(StringUtil2.padLeft(stat.trim(), 2));
    builder.append(" ");
    builder.append(StringUtil2.padLeft(coun.trim(), 2));
    builder.append(" ");
    builder.append(Format.i(slat, 5));
    builder.append(" ");
    builder.append(Format.i(slon, 6));
    builder.append(" ");
    builder.append(Format.i(selv, 5));
    builder.append(" ");
    builder.append(Format.i(spri, 2));
    builder.append(" ");
    builder.append(StringUtil2.padLeft(swfo.trim(), 3));
    return builder.toString();
  }

  public int getNobs() {
    return -1;
  }

  /**
   * Set the STID
   *
   * @param value new value
   */
  public void setSTID(String value) {
    stid = value;
  }

  /**
   * Get the STID
   *
   * @return the STID value
   */
  public String getSTID() {
    return stid;
  }

  /**
   * Set the STNM
   *
   * @param value new value
   */
  public void setSTNM(int value) {
    stnm = value;
  }

  /**
   * Get the STNM
   *
   * @return the STNM value
   */
  public int getSTNM() {
    return stnm;
  }

  /**
   * Set the STAT
   *
   * @param value new value
   */
  public void setSTAT(String value) {
    stat = value;
  }

  /**
   * Get the STAT
   *
   * @return the STAT value
   */
  public String getSTAT() {
    return stat;
  }

  /**
   * Set the COUN
   *
   * @param value new value
   */
  public void setCOUN(String value) {
    coun = value;
  }

  /**
   * Get the COUN
   *
   * @return the COUN value
   */
  public String getCOUN() {
    return coun;
  }

  /**
   * Set the STD2
   *
   * @param value new value
   */
  public void setSTD2(String value) {
    std2 = value;
  }

  /**
   * Get the STD2
   *
   * @return the STD2 value
   */
  public String getSTD2() {
    return std2;
  }

  /**
   * Set the SWFO
   *
   * @param value new value
   */
  public void setSWFO(String value) {
    swfo = value;
  }

  /**
   * Get the SWFO
   *
   * @return the SWFO value
   */
  public String getSWFO() {
    return swfo;
  }

  /**
   * Set the WFO2
   *
   * @param value new value
   */
  public void setWFO2(String value) {
    wfo2 = value;
  }

  /**
   * Get the WFO2
   *
   * @return the WFO2 value
   */
  public String getWFO2() {
    return wfo2;
  }

  /**
   * Set the SLAT
   *
   * @param value new value
   */
  public void setSLAT(int value) {
    slat = value;
  }

  /**
   * Get the SLAT
   *
   * @return the SLAT value
   */
  public int getSLAT() {
    return slat;
  }

  /**
   * Set the SLON
   *
   * @param value new value
   */
  public void setSLON(int value) {
    slon = value;
  }

  /**
   * Get the SLON
   *
   * @return the SLON value
   */
  public int getSLON() {
    return slon;
  }

  /**
   * Set the SELV
   *
   * @param value new value
   */
  public void setSELV(int value) {
    selv = value;
  }

  /**
   * Get the SELV
   *
   * @return the SELV value
   */
  public int getSELV() {
    return selv;
  }

  /**
   * Set the SPRI
   *
   * @param value new value
   */
  public void setSPRI(int value) {
    spri = value;
  }

  /**
   * Get the SPRI
   *
   * @return the SPRI value
   */
  public int getSPRI() {
    return spri;
  }

  // Station interface stuff  have this class extend when we
  // decide where the interface lives

  /**
   * Get the latitude in decimal degrees north
   *
   * @return the latitude
   */
  public double getLatitude() {
    if (slat == GempakConstants.IMISSD) {
      return slat;
    }
    return slat / 100.;
  }

  /**
   * Get the longitude in decimal degrees east
   *
   * @return the longitude
   */
  public double getLongitude() {
    if (slon == GempakConstants.IMISSD) {
      return slon;
    }
    return slon / 100.;
  }

  /**
   * Get the altitude in meters;  missing = NaN.
   *
   * @return the altitude
   */
  public double getAltitude() {
    return selv;
  }

  /**
   * Get the Station name. Must be unique within the collection
   *
   * @return the station id
   */
  public String getName() {
    return stid.trim() + std2.trim();
  }

  /**
   * Get the Station description
   *
   * @return station description
   */
  public String getDescription() {
    return sdesc;
  }

  /**
   * Set the station description
   *
   * @param desc the description
   */
  public void setDescription(String desc) {
    sdesc = desc;
  }

  /**
   * Get the WMO Station ID as a string
   *
   * @return the WMO id
   */
  public String getWmoId() {
    String wmoID = "";
    if (!(stnm == GempakConstants.IMISSD)) {
      wmoID = String.valueOf((int) (stnm / 10));
    }
    return wmoID;
  }

  /**
   * Compare this to another
   *
   * @param o other object
   * @return comparison on nam
   */
  public int compareTo(Station o) {
    return getName().compareTo(o.getName());
  }

  /**
   * Are either lat or lon missing?
   *
   * @return true if lat or lon is missing
   */
  public boolean isMissing() {
    return ((slat == GempakConstants.IMISSD)
            || (slon == GempakConstants.IMISSD));
  }

  /**
   * Get the lat/lon location
   *
   * @return lat/lon location
   */
  public LatLonPoint getLatLon() {
    return new LatLonPointImpl(getLatitude(), getLongitude());
  }

  /**
   * Set the row or column index for this station
   *
   * @param rowOrCol the index
   */
  public void setIndex(int rowOrCol) {
    index = rowOrCol;
  }

  /**
   * Get the row or column index for this station
   *
   * @return the index
   */
  public int getIndex() {
    return index;
  }
}


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

package opendap.dap;

import opendap.dap.parsers.DDSXMLParser;

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

/**
 * This class is used to hold arrays of other OPeNDAP data. The elements of the
 * array can be simple or compound data types. There is no limit on the
 * number of dimensions an array can have, or on the size of each dimension.
 *

 * If desired, the user can give each dimension of an array a name. You can,
 * for example, have a 360x180 array of temperatures, covering the whole
 * globe with one-degree squares. In this case, you could name the first
 * dimension "Longitude" and the second dimension "Latitude". This can
 * help prevent a great deal of confusion.
 *

 * The DArray is used as part of the DGrid class,
 * where the dimension names are crucial to its structure. The dimension names
 * correspond to "Map" vectors, holding the actual values for that column of
 * the array.
 *

 * Each array dimension carries with it its own projection information. The
 * projection inforamtion takes the form of three integers: the start, stop,
 * and stride values. This is clearest with an example. Consider a
 * one-dimensional array 10 elements long. If the start value of the
 * dimension constraint is 3, then the constrained array appears to be seven
 * elements long. If the stop value is changed to 7, then the array appears
 * to be five elements long. If the stride is changed to two, the array will
 * appear to be 3 elements long. Array constraints are written as
 * [start:stride:stop].
 *

 *

 * A = [1 2 3 4 5 6 7 8 9 10]
 * A[3::] = [4 5 6 7 8 9 10]
 * A[3::7] = [4 5 6 7 8]
 * A[3:2:7] = [4 6 8]
 * A[0:3:9] = [1 4 7 10]
 *

 *

 * NB: OPeNDAP uses zero-based indexing.
 *
 * @author jehamby
 * @version $Revision: 19676 $
 * @see DGrid
 * @see DVector
 * @see BaseType
 */
public class DArray extends DVector
{
    /**
     * A Vector of DArrayDimension information (i.e. the shape)
     */
    protected Vector<DArrayDimension> dimVector;

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

    /**
     * Constructs a new DArray with name n.
     *
     * @param n the name of the variable.
     */
    public DArray(String n) {
        super(n);
        dimVector = new Vector<DArrayDimension>();
    }

    /**
     * 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 "Array";
    }

    /**
     * Checks for internal consistency.  For DArray, verify that
     * the dimension vector is not empty.
     *
     * @param all for complex constructor types, this flag indicates whether to
     *            check the semantics of the member variables, too.
     * @throws BadSemanticsException if semantics are bad, explains why.
     * @see BaseType#checkSemantics(boolean)
     */
    public void checkSemantics(boolean all)
            throws BadSemanticsException {
        super.checkSemantics(all);

        if (dimVector.isEmpty())
            throw new BadSemanticsException("An array variable must have dimensions");
    }

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

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

        //os.println("DArray.printDecl()");

        getPrimitiveVector().printDecl(os, space, false, constrained);
        for (Enumeration e = dimVector.elements(); e.hasMoreElements();) {
            DArrayDimension d = (DArrayDimension) e.nextElement();
            os.print("[");
            String name = d.getEncodedName();
            if (name != null && name.length() > 0)
                os.print(d.getEncodedName() + " = ");
            os.print(d.getSize() + "]");
        }
        if (print_semi)
            os.println(";");
    }

    /**
     * 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 pw           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 pw, String space, boolean print_decl_p) {
        // print the declaration if print decl is true.
        // for each dimension,
        //   for each element,
        //     print the array given its shape, number of dimensions.
        // Add the `;'

        if (print_decl_p) {
            printDecl(pw, space, false);
            pw.print(" = ");
        }

        int dims = numDimensions();
        int shape[] = new int[dims];
        int i = 0;
        for (Enumeration e = dimVector.elements(); e.hasMoreElements();) {
            DArrayDimension d = (DArrayDimension) e.nextElement();
            shape[i++] = d.getSize();
        }

        printArray(pw, 0, dims, shape, 0);

        if (print_decl_p)
            pw.println(";");

        pw.flush();
    }

    /**
     * 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 dimension array.
     * @return the number of elements written.
     */
    private int printArray(PrintWriter os, int index, int dims, int shape[],
                           int offset) {
        if (dims == 1) {
            os.print("{");
            for (int i = 0; i < shape[offset] - 1; i++) {
                getPrimitiveVector().printSingleVal(os, index++);
                os.print(", ");
            }
            getPrimitiveVector().printSingleVal(os, index++);
            os.print("}");
            return index;
        } else {
            os.print("{");
            for (int i = 0; i < shape[offset] - 1; i++) {
                index = printArray(os, index, dims - 1, shape, offset + 1);
                os.print(",");
            }
            index = printArray(os, index, dims - 1, shape, offset + 1);
            os.print("}");
            return index;
        }
    }

    /**
     * Given a size and a name, this function adds a dimension to the
     * array.  For example, if the DArray is already 10 elements
     * long, calling appendDim with a size of 5 will transform the
     * array into a 10x5 matrix.  Calling it again with a size of 2 will
     * create a 10x5x2 array, and so on.
     *
     * @param size the size of the desired new dimension.
     * @param name the name of the new dimension.
     */
    public void appendDim(int size, String name) {
        DArrayDimension newDim = new DArrayDimension(size, name);
        dimVector.addElement(newDim);
        newDim.setContainer(this);
    }

    /**
     * Add a dimension to the array.  Same as appendDim(size, null).
     *
     * @param size the size of the desired new dimension.
     * @see DArray#appendDim(int, String)
     */
    public void appendDim(int size) {
        appendDim(size, null);
    }

    /**
     * Returns an Enumeration of DArrayDimensions
     * in this array.
     *
     * @return an Enumeration of DArrayDimensions
     *         in this array.
     */
    public final Enumeration getDimensions() {
        return dimVector.elements();
    }

    /**
     * Returns the number of dimensions in this array.
     *
     * @return the number of dimensions in this array.
     */
    public final int numDimensions() {
        return dimVector.size();
    }

    /**
     * Use this method to "squeeze" out all of the array dimensions whose
     * size is equal to 1.
     *

     * Many queries that contstrain Arrays return an Array that has dimensions
     * whose size has been reduced to 1. In effect that the dimension no longer
     * really exists, except as a notational convention for tracking the
     * hyperslab that the array represents. Since many clients have difficulty
     * handling n-dimensional arrays this method was added to allow the client
     * to easily "squeeze" the "extra" dimensions out of the array.
     *
     *
     */
    public void squeeze(){

        if(dimVector.size()==1)
            return;

        Vector<DArrayDimension> squeezeCandidates = new Vector<DArrayDimension>();

        for (DArrayDimension dim: dimVector){
            if(dim.getSize()==1)
                squeezeCandidates.add(dim);
        }

        if(squeezeCandidates.size()==dimVector.size())
            squeezeCandidates.remove(squeezeCandidates.size()-1);

        //LogStream.out.println("DArray.squeeze(): Removing "+
        //        squeezeCandidates.size()+" dimensions of size 1.");

        dimVector.removeAll(squeezeCandidates);

    }

    /**
     * Returns the DArrayDimension object for
     * the dimension requested. It makes sure that the dimension requested
     * exists.
     */
    public DArrayDimension getDimension(int dimension) throws InvalidDimensionException {

        // QC the passed dimension
        if (dimension < dimVector.size())
            return  dimVector.get(dimension);
        else
            throw new InvalidDimensionException("DArray.getDimension(): Bad dimension request: dimension > # of dimensions");
    }

    /**
     * Returns the DArrayDimension object for
     * the first dimension.
     */
    public DArrayDimension getFirstDimension() {
        return dimVector.get(0);
    }

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

        pw.print(pad + "<Array");
        if (getEncodedName() != null) {
            pw.print(" name=\"" +
                    DDSXMLParser.normalizeToXML(getClearName()) + "\"");
        }
        pw.println(">");

        printXMLcore(pw, pad, constrained);

        pw.println(pad + "</Array>");

    }

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

        pw.print(pad + "<Map");
        if (getEncodedName() != null) {
            pw.print(" name=\"" +
                    DDSXMLParser.normalizeToXML(getClearName()) + "\"");
        }
        pw.println(">");

        printXMLcore(pw, pad, constrained);

        pw.println(pad + "</Map>");

    }

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

        Enumeration e = getAttributeNames();
        while (e.hasMoreElements()) {
            String aName = (String) e.nextElement();

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

        }

        BaseType bt = null;

        PrimitiveVector pv = getPrimitiveVector();
        // *** Nathan, can we get rid of this 'if?' 05/15/03 jhrg
        //Coverity[IDENTICAL_BRANCHES]
        if (pv instanceof BaseTypePrimitiveVector) {
            bt = pv.getTemplate();
        } else {
            bt = pv.getTemplate();
        }

        String nameCache = bt.getEncodedName();

        bt.setEncodedName(null);

        bt.printXML(pw, pad + "\t", constrained);

        bt.setEncodedName(nameCache);

        Enumeration dae = getDimensions();
        while (dae.hasMoreElements()) {
            DArrayDimension dad = (DArrayDimension) dae.nextElement();
            int size = dad.getSize();
            String name = dad.getEncodedName();
            if (name == null) {
                pw.println(pad + "\t" + "<dimension size=\"" + size + "\"/>");
            } else {
                pw.println(pad + "\t" + "<dimension name=\"" +
                        DDSXMLParser.normalizeToXML(name) +
                        "\" size=\"" + size + "\"/>");
            }
        }
    }

    public void printConstraint(PrintWriter os)
    {
	if(getParent() != null && !(getParent() instanceof DDS)) {
	    ((BaseType)getParent()).printConstraint(os);
	    os.print(".");
	}
        os.print(getEncodedName());
	for(int i=0;i<dimVector.size();i++) {
	    DArrayDimension dim = (DArrayDimension)dimVector.get(i);
	    dim.printConstraint(os);
	}
    }

    /**
     * Returns a clone of this Array.
     * See DAPNode.cloneDag()
     *
     * @param map track previously cloned nodes
     * @return a clone of this object.
     */
    public DAPNode cloneDAG(CloneMap map)
        throws CloneNotSupportedException
    {
        DArray a = (DArray) super.cloneDAG(map);
        a.dimVector = new Vector<DArrayDimension>();
        for (int i = 0; i < dimVector.size(); i++) {
            DArrayDimension d =  dimVector.elementAt(i);
            DArrayDimension dclone = (DArrayDimension)cloneDAG(map,d);
            dclone.setContainer(a);
            a.dimVector.addElement(dclone);
        }
        return a;
    }

}


package ucar.nc2.grib.grib2.tools;

import com.google.common.collect.ImmutableList;
import java.io.File;
import java.util.Formatter;
import ucar.nc2.grib.grib2.table.EcmwfCodeTable;
import ucar.nc2.grib.grib2.table.Grib2CodeTableInterface;
import ucar.nc2.grib.grib2.table.WmoCodeFlagTables;
import ucar.nc2.grib.grib2.table.WmoCodeFlagTables.TableType;
import ucar.nc2.wmo.Util;

public class EcmwfCodeTableCompare {

  private static boolean verbose = false;

  // Compare 2 tables, print report.
  public static void compareTables(Grib2CodeTableInterface t1, Grib2CodeTableInterface t2, Formatter f) {
    int extra = 0;
    int conflict = 0;
    f.format("%s%n", t2.getName());
    for (Grib2CodeTableInterface.Entry p1 : t1.getEntries()) {
      if (t1.getEntry(p1.getCode()) == null) {
        f.format(" ERROR %s missing own code %d%n", t1.getShortName(), p1.getCode());
      }
      Grib2CodeTableInterface.Entry p2 = t2.getEntry(p1.getCode());
      if (p2 == null) {
        extra++;
        if (verbose) {
          f.format("  %s missing %s%n", t2.getShortName(), p1);
        }
      } else {
        if (!Util.equivilantName(p1.getName(), p2.getName())) {
          f.format("  p1=%10s %s%n", p1.getCode(), p1.getName());
          f.format("  p2=%10s %s%n", p2.getCode(), p2.getName());
          conflict++;
        }
      }
    }

    int missing = 0;
    for (Grib2CodeTableInterface.Entry p2 : t2.getEntries()) {
      if (t2.getEntry(p2.getCode()) == null) {
        f.format(" ERROR %s missing own code %d%n", t2.getShortName(), p2.getCode());
      }
      Grib2CodeTableInterface.Entry p1 = t1.getEntry(p2.getCode());
      if (p1 == null) {
        missing++;
        f.format("  %s missing %s%n", t1.getShortName(), p2);
        t1.getEntry(p2.getCode());
      }
    }
    if (conflict > 0 || missing > 0) {
      f.format(" ***Conflicts=%d extra=%d missing=%s%n", conflict, extra, missing);
    }
  }

  public static void readCodeTable(int discipline, int category) {
    WmoCodeFlagTables tables = WmoCodeFlagTables.getInstance();
    String tableName = String.format("%d.%d", discipline, category);
    TableType type = tables.getTableType(tableName);
    if (type == null) {
      System.out.printf("================================================================%n");
      System.out.printf("   No WMO table that matches ECMWF table %s%n", tableName);
      return;
    }
    if (type == TableType.code) {
      Grib2CodeTableInterface latest = WmoCodeFlagTables.getInstance().getCodeTable(discipline, category);
      System.out.printf("================================================================%n");
      System.out.printf("%s%n", latest.getName());

      for (int version = 21; version >= 0; version--) {
        Grib2CodeTableInterface next = EcmwfCodeTable.factory(version, discipline, category);
        if (next == null) {
          System.out.printf("Missing version %d%n", version);
        } else {
          Formatter out = new Formatter();
          compareTables(latest, next, out);
          System.out.printf("%s", out);
          latest = next;
        }
        if (!verbose) break;
      }
    }
  }

  public static void main(String[] args) {
    final String PATH = "/usr/local/google/home/jlcaron/github/thredds/grib/src/main/resources/resources/grib2/ecmwf/tables/21";
    System.out.printf("EcmwfCodeTableCompare%n");
    System.out.printf("  ECMWF = %s%n", PATH);
    System.out.printf("  WMO   = %s%n", WmoCodeFlagTables.standard.getResourceName());

    File root = new File(PATH);
    ImmutableList<String> children = ImmutableList.copyOf(root.list());
    children.stream().sorted().forEach(filename -> {
      if (!filename.startsWith("4.2.") && !filename.startsWith("4.1.")) {
        String[] split = filename.split("\\.");
        try {
          int discipline = Integer.parseInt(split[0]);
          int category = Integer.parseInt(split[1]);
          readCodeTable(discipline, category);
        } catch (Exception e) {
          System.out.printf("================================================================%n");
          System.out.printf("  Unparsed ECMWF table %s%n", filename);
        }
      }
    });
  }

}