/////////////////////////////////////////////////////////////////////////////
// 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.servers.SDArray;
import opendap.dap.parsers.DDSXMLParser;

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

/**
 * This class holds a DArray and a set of "Map"
 * vectors.  The Map vectors are one-dimensional arrays corresponding
 * to each dimension of the central Array.  Using this scheme, a
 * Grid can represent, in a rectilinear array, data which is not
 * in reality rectilinear.  An example will help make this clear.
 *

 * Assume that the following array contains measurements of some real
 * quantity, conducted at nine different points in space:
 *

 *

 * A = [ 1  2  3  4 ]
 *     [ 2  4  6  8 ]
 *     [ 3  6  9  12]
 *

 *

 * To locate this Array in the real world, we could note the
 * location of one corner of the grid, and the grid spacing.  This would allow
 * us to calculate the location of any of the other points of the
 * Array.
 *

 * This approach will not work, however, unless the grid spacing is
 * precisely regular.  If the distance between Row 1 and Row 2 is not
 * the same as the distance between Row 2 and Row 3, the scheme will
 * break down.  The solution is to equip the Array with two
 * Map vectors that define the location of each row or column of
 * the array:
 *

 *

 *       A = [ 1  2  3  4 ] Row = [ 0 ]
 *           [ 2  4  6  8 ]       [ 3 ]
 *           [ 3  6  9  12]       [ 8 ]
 *

 *  Column = [ 0  2  8  27]
 *

 *

 * The real location of the point in the first row and column of the
 * array is now exactly fixed at (0,0), and the point in the last row
 * and last column is at (8,27).
 *
 * @author jehamby
 * @version $Revision: 19676 $
 * @see BaseType
 * @see DArray
 */
public class DGrid extends DConstructor implements ClientIO {
    /**
     * The Array part of the DGrid
     */
    public static final int ARRAY = 1;

    /**
     * The Map part of the DGrid
     */
    public static final int MAPS = 2;

    /**
     * The Array component of this DGrid.
     */
    protected DArray arrayVar;

    /**
     * The Map component of this DGrid.
     */
    protected Vector mapVars;

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

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

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

    /**
     * Returns the number of variables contained in this object. For simple and
     * vector type variables, it always returns 1. To count the number
     * of simple-type variable in the variable tree rooted at this variable, set
     * leaves to true.
     *
     * @param leaves If true, count all the simple types in the `tree' of
     *               variables rooted at this variable.
     * @return the number of contained variables.
     */
    public int elementCount(boolean leaves) {
        if (!leaves)
            return mapVars.size() + 1; // Number of Maps plus 1 Array component
        else {
            int count = 0;
            for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
                BaseType bt = (BaseType) e.nextElement();
                count += bt.elementCount(leaves);
            }
            count += arrayVar.elementCount(leaves);
            return count;
        }
    }

    /**
     * Adds a variable to the container.
     *
     * @param v    the variable to add.
     * @param part the part of the DGrid to be modified.  Allowed
     *             values are ARRAY or MAPS.
     * @throws IllegalArgumentException if an invalid part was given.
     */
    public void addVariable(BaseType v, int part) {

        if (!(v instanceof DArray)) throw new IllegalArgumentException(
                "Grid `" + getEncodedName() +
                        "'s' member `" + arrayVar.getEncodedName() +
                        "' must be an array");

        v.setParent(this);

        switch (part) {

            case ARRAY:
                arrayVar = (DArray) v;
                return;

            case MAPS:
                mapVars.addElement(v);
                return;

            default:
                throw new IllegalArgumentException("addVariable(): Unknown Grid part");
        }
    }

    /**
     * Returns the named variable.
     *
     * @param name the name of the variable.
     * @return the named variable.
     * @throws NoSuchVariableException if the named variable does not
     *                                 exist in this container.
     */
    public BaseType getVariable(String name) throws NoSuchVariableException {

        int dotIndex = name.indexOf('.');

        if (dotIndex != -1) {  // name contains "."

            String aggregate = name.substring(0, dotIndex);
            String field = name.substring(dotIndex + 1);

            BaseType aggRef = getVariable(aggregate);
            if (aggRef instanceof DConstructor)
                return ((DConstructor) aggRef).getVariable(field);  // recurse
            else
                ; // fall through to throw statement
        } else {
            if (arrayVar.getEncodedName().equals(name))
                return arrayVar;

            for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
                BaseType v = (BaseType) e.nextElement();
                if (v.getEncodedName().equals(name))
                    return v;
            }
        }
        throw new NoSuchVariableException("DGrid.getVariable() No such variable: '" + name + "'");
    }

    /**
     * Gets the indexed variable. For a DGrid the index 0 returns the DArray and
     * indexes 1 and higher return the associated map Vectors.
     *
     * @param index the index of the variable in the Vector Vars.
     * @return the indexed variable.
     * @throws NoSuchVariableException if the named variable does not
     *                                 exist in this container.
     */
    public BaseType getVar(int index)
            throws NoSuchVariableException
    {
        if (index == 0) {
            return (arrayVar);
        } else {
            int i = index - 1;
            if (i < mapVars.size())
                return ((BaseType) mapVars.elementAt(i));
            else
                throw new NoSuchVariableException("DGrid.getVariable() No Such variable: " + index + " - 1");
        }
    }

    /**
     * Get the number of contained variables (for use with getVar()
     * @return the number of contained variables
     */
    public int getVarCount()
    {
	return mapVars.size() + 1;
    }

    /**
     * Private class for implemantation of the Enumeration. Because
     * DStructure and DSequence are simpler classes and use a single Vector,
     * their implementations of getVariables aren't as fancy.
     */
    class EnumerateDGrid implements Enumeration {
        boolean array;
        Enumeration e;

        EnumerateDGrid() {
            array = false;    // true when the array is/has being/been
            // visited
            e = mapVars.elements();
        }

        public boolean hasMoreElements() {
            return (array == false) || e.hasMoreElements();
        }

        public Object nextElement() {
            if (!array) {
                array = true;
                return arrayVar;
            } else {
                return e.nextElement();
            }
        }
    }

    /**
     * Return an Enumeration that can be used to iterate over the members of
     * a Structure. This implementation provides access to the elements of
     * the Structure. Each Object returned by the Enumeration can be cast to
     * a BaseType.
     *
     * @return An Enumeration
     */
    public Enumeration getVariables() {
        return new EnumerateDGrid();
    }
    /**
     * Checks for internal consistency.  For DGrid, verify that
     * the map variables have unique names and match the number of dimensions
     * of the array variable.
     *
     * @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);

        Util.uniqueNames(mapVars, getEncodedName(), getTypeName());

        if (arrayVar == null)
            throw new BadSemanticsException("DGrid.checkSemantics(): Null grid base array in `"
                    + getEncodedName() + "'");

        // check semantics of array variable
        arrayVar.checkSemantics(all);

        // enough maps?
        if (mapVars.size() != arrayVar.numDimensions())
            throw new BadSemanticsException("DGrid.checkSemantics(): The number of map variables for grid `"
                    + getEncodedName() + "' does not match the number of dimensions of `"
                    + arrayVar.getEncodedName() + "'");

        //----- I added this next test 12/3/99. As soon as I did I questioned whether or not
        //----- it adds any value. ie: Can it ever happen that this test fails? I don't think
        //----- so now that I have written it...  ndp 12/3/99

        // Is the size of the maps equal to the size of the cooresponding dimensions?
        Enumeration emap = mapVars.elements();

        Enumeration edims = arrayVar.getDimensions();
        int dim = 0;
        while (emap.hasMoreElements() && edims.hasMoreElements()) {

            DArray thisMapArray = (DArray) emap.nextElement();
            Enumeration ema = thisMapArray.getDimensions();
            DArrayDimension thisMapDim = (DArrayDimension) ema.nextElement();

            DArrayDimension thisArrayDim = (DArrayDimension) edims.nextElement();

            if (thisMapDim.getSize() != thisArrayDim.getSize()) {

                throw new BadSemanticsException("In grid '" +
                        getEncodedName() +
                        " The size of dimension " +
                        dim + " in the array component '" +
                        arrayVar.getEncodedName() +
                        "is not equal to the size of the coresponding map vector '" +
                        thisMapArray.getEncodedName() + ".");
            }
            dim++;
        }
        //----- end  ndp 12/3/99

    }

    /**
     * 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)
     */
    //Coverity[CALL_SUPER]
    public void printDecl(PrintWriter os, String space, boolean print_semi, boolean constrained) {
        os.println(space + getTypeName() + " {");
        os.println(space + " ARRAY:");
        arrayVar.printDecl(os, space + "    ", true);
        os.println(space + " MAPS:");
        for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
            BaseType bt = (BaseType) e.nextElement();
            bt.printDecl(os, space + "    ", true);
        }
        os.print(space + "} " + getEncodedName());
        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 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.print(" = ");
        }

        os.print("{ ARRAY: ");
        arrayVar.printVal(os, "", false);

        os.print(" MAPS: ");
        for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
            BaseType bt = (BaseType) e.nextElement();
            bt.printVal(os, "", false);
            if (e.hasMoreElements())
                os.print(", ");
        }
        os.print(" }");

        if (print_decl_p)
            os.println(";");
    }

    /**
     * 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.
     * @throws DataReadException if an unexpected value was read.
     * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
     */
    public synchronized void deserialize(DataInputStream source,
                                         ServerVersion sv,
                                         StatusUI statusUI)
            throws IOException, DataReadException {
        arrayVar.deserialize(source, sv, statusUI);
        for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
            if (statusUI != null && statusUI.userCancelled())
                throw new DataReadException("DGrid.deserialize(): User cancelled");
            ClientIO bt = (ClientIO) e.nextElement();
            bt.deserialize(source, sv, statusUI);
        }
    }

    /**
     * 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 {
        arrayVar.externalize(sink);
        for (Enumeration e = mapVars.elements(); e.hasMoreElements();) {
            ClientIO bt = (ClientIO) e.nextElement();
            bt.externalize(sink);
        }
    }

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

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

        Enumeration e = getAttributeNames();

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

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

        }

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

        e = mapVars.elements();
        while (e.hasMoreElements()) {
            DArray map = (DArray) e.nextElement();

            map.printAsMapXML(pw, pad + "\t", constrained);

        }

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

    // Export for testing
    public DArray getArray() {return arrayVar;}
    public Vector<DArrayDimension> getArrayDims() {return arrayVar.dimVector;}

    /**
         * How many prohected components of this Grid object?
         *
         * @return The number of projected components.
         */
        public int projectedComponents(boolean constrained) {
            int comp;

            if (constrained) {
                comp = ((DArray)arrayVar).isProject() ? 1 : 0;

                Enumeration e = mapVars.elements();

                while (e.hasMoreElements()) {
                    if (((DArray) e.nextElement()).isProject())
                        comp++;
                }
            } else {
                comp = 1 + mapVars.size();
            }

            return comp;
        }

    /**
     * When projected (using whatever the current constraint provides in the way
     * of a projection) am I still a Grid?
     *
     * @return True if projected grid is still a grid. False otherwise.
     */

    public boolean projectionYieldsGrid(boolean constrained) {

        if(!constrained) return true;

        // For each dimension in the Array part, check the corresponding Map
        // vector to make sure it is present in the projected Grid. If for each
        // projected dimension in the Array component, there is a matching Map
        // vector, then the Grid is valid.
        boolean valid = true;

        // Don't bother checking if the Array component is not included.
        if (!((SDArray) arrayVar).isProject())
            return false;

        int nadims = arrayVar.numDimensions();
        int nmaps =  getVarCount()-1;
        //Enumeration aDims = arrayVar.getDimensions();
        //Enumeration e = mapVars.elements();
        //while (valid && e.hasMoreElements() && aDims.hasMoreElements()) {

        if(nadims != nmaps)
            valid = false;
        else for(int d=0;d<nadims;d++) {
            try {

            DArrayDimension thisDim = arrayVar.getDimension(d); //(DArrayDimension) aDims.nextElement();
            SDArray mapArray = (SDArray)getVar(d+1); //e.nextElement();
            DArrayDimension mapDim = mapArray.getFirstDimension();

            if (thisDim.getSize() > 0) {
                // LogStream.out.println("Dimension Contains Data.");

                if (mapArray.isProject()) { // This map vector better be projected!
                    // LogStream.out.println("Map Vector Projected, checking projection image...");

                    // Check the matching Map vector; the Map projection must equal
                    // the Array dimension projection

                    // wrong: valid at this point might have been false: valid = true;
                    valid = valid && mapDim.getStart() == thisDim.getStart();
                    valid = valid && mapDim.getStop() == thisDim.getStop();
                    valid = valid && mapDim.getStride() == thisDim.getStride();
                } else {
                    // LogStream.out.println("Map Vector not Projected.");
                    valid = false;
                }

            } else {
                // LogStream.out.println("Dimension empty. Verifying corresponding Map vector not projected...");
                // Corresponding Map vector must be excluded from the
                // projection or it's not a grid.
                valid = !mapArray.isProject();
            }

            }  catch(Exception e) {
                Util.check(e);
                valid=false;
                break;
            }

        }

        //if (e.hasMoreElements() != aDims.hasMoreElements()) valid = false;

        return valid;
    }

    /**
     * Returns a clone of this DGrid.
     * See DAPNode.cloneDag()
     *
     * @param map track previously cloned nodes
     * @return a clone of this object.
     */
    public DAPNode cloneDAG(CloneMap map)
        throws CloneNotSupportedException
    {
        DGrid g = (DGrid) super.cloneDAG(map);
        g.arrayVar = (DArray) cloneDAG(map,arrayVar);
        g.mapVars = new Vector();
        for (int i = 0; i < mapVars.size(); i++) {
            BaseType bt = (BaseType) mapVars.elementAt(i);
	        BaseType btclone = (BaseType)cloneDAG(map,bt);
            g.mapVars.addElement(btclone);
        }
        return g;
    }

}


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

package ucar.nc2.iosp.grads;

import ucar.nc2.constants.CDM;

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

/**
 * Hold information about a GradDimension
 * @author         Don Murray - CU/CIRES
 */
public class GradsDimension {

    /** linear type mapping */
    public static final String LINEAR = "LINEAR";

    /** levels type mapping */
    public static final String LEVELS = "LEVELS";

    /** the name of the dimension */
    private String name;

    /** the size of the dimension */
    private int size;

    /** the type of mapping (LINEAR, LEVELS, etc) */
    private String mapping;

    /** list of levels or params for LINEAR */
    private List<String> levels;

    /** the actual levels */
    private double[] levelVals;

    /** the unit */
    private String unitName = "";

    /**
     * Make a new GradsDimension from the values
     *
     * @param name  the dimension name
     * @param size  the dimension size
     * @param mapping  the dimension mapping type
     */
    public GradsDimension(String name, int size, String mapping) {
        this.name    = name;
        this.size    = size;
        this.mapping = mapping;
        levels       = new ArrayList<String>();
        if (name.equalsIgnoreCase(GradsDataDescriptorFile.XDEF)) {
            unitName = CDM.LON_UNITS;
        } else if (name.equalsIgnoreCase(GradsDataDescriptorFile.YDEF)) {
            unitName = CDM.LAT_UNITS;
        } else if (name.equalsIgnoreCase(GradsDataDescriptorFile.ZDEF)) {
            unitName = "hPa";
        }
    }

    /**
     * Add a level to the list of levels
     *
     * @param level level to add
     */
    protected void addLevel(String level) {
        levels.add(level);
    }

    /**
     * Get the levels
     *
     * @return  the list of levels
     */
    protected List<String> getLevels() {
        return levels;
    }

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

    /**
     * Get the size of this dimension
     *
     * @return  the size
     */
    public int getSize() {
        return size;
    }

    /**
     * Get the values
     *
     * @return  the values
     */
    public double[] getValues() {
        if (levelVals == null) {
            levelVals = makeLevelValues();
        }

        return levelVals;
    }

    /**
     * Get the units
     *
     * @return  the units
     */
    public String getUnit() {
        return unitName;
    }

    /**
     * Set the unit
     *
     * @param unit  the unit
     */
    protected void setUnit(String unit) {
        unitName = unit;
    }

    /**
     * Get the mapping type
     *
     * @return  the mapping type
     */
    public String getType() {
        return mapping;
    }

    /**
     * Make the level values from the specifications
     *
     * @return the level values
     */
    protected double[] makeLevelValues() {
        if (levels == null) {
            return null;
        }
        if (levels.size() != size) {
            // do someting
        }
        double[] vals = new double[size];
        if (mapping.equalsIgnoreCase(LEVELS)) {
            for (int i = 0; i < vals.length; i++) {
                vals[i] = Double.parseDouble(levels.get(i));
            }
        } else if (mapping.equalsIgnoreCase(LINEAR)) {
            double start = 0;
            double inc   = 0;
            start = Double.parseDouble(levels.get(0));
            inc   = Double.parseDouble(levels.get(1));
            for (int i = 0; i < size; i++) {
                vals[i] = start + i * inc;
            }
            // TODO: figure out a better way to do this in case they don't start with gaus (e.g. MOM32)
        } else if (mapping.toLowerCase().startsWith("gaus")) {
            vals = GradsUtil.getGaussianLatitudes(mapping,
                    (int) Double.parseDouble(levels.get(0)), size);
        }
        // sanity check on z units
        if (name.equals(GradsDataDescriptorFile.ZDEF)) {
            for (int i = 0; i < vals.length; i++) {
                double val = vals[i];
                if (val > 1050) {
                    unitName = "Pa";

                    break;
                } else if (val < 10) {
                    // sometimes it's just a level number
                    // probably should be something else, but dimensionless
                    unitName = "";

                    break;
                }
            }
        }
        return vals;
    }

    /**
     * Return a String representation of this object
     *
     * @return a String representation of this object
     */
    public String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append("Dimension: ");
        buf.append(name.toUpperCase());
        buf.append("\n");
        buf.append("\tSize: ");
        buf.append(size);
        buf.append("\n");
        buf.append("\tLevels Size: ");
        buf.append(levels.size());
        buf.append("\n");
        buf.append("\tMappingType: ");
        buf.append(mapping.toUpperCase());
        buf.append("\n");
        buf.append("\tLevels: ");
        buf.append(levels.toString());
        buf.append("\n");
        buf.append("\tUnits: ");
        buf.append(unitName);
        buf.append("\n");

        return buf.toString();
    }
}


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

package dap4.core.ce.parser;

import dap4.core.ce.CEAST;
import dap4.core.dmr.*;
import dap4.core.dmr.parser.ParseException;
import dap4.core.util.*;

import java.util.*;

public class CEParserImpl extends CEBisonParser
{

    //////////////////////////////////////////////////
    // Constants

    static final long UNDEFINED = dap4.core.util.Slice.UNDEFINED;

    //////////////////////////////////////////////////
    // static variables

    static protected int globaldebuglevel = 0;

    //////////////////////////////////////////////////
    // static methods

    static public void setGlobalDebugLevel(int level) {globaldebuglevel = level;}
    //////////////////////////////////////////////////
    // Instance variables

    protected DapDataset template = null;

    protected Map<String, Slice> dimdefs = new HashMap<String, Slice>();

    protected CEAST constraint = null;

    //////////////////////////////////////////////////
    // Constructors

    public CEParserImpl(DapDataset template)
            throws ParseException
    {
        super(null);
        CELexer lexer = new CELexer(this);
        setLexer(lexer);
        this.template = template;
	if(globaldebuglevel > 0)
	    super.setDebugLevel(globaldebuglevel);
    }

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

    public CEAST getCEAST()
    {
        return constraint;
    }

    //////////////////////////////////////////////////
    // Parser API

    public boolean
    parse(String document)
            throws ParseException
    {
        ((CELexer) getLexer()).setText(document);
        return super.parse();
    }

    //////////////////////////////////////////////////
    // Parser primary actions

    @Override
    CEAST
    constraint(CEAST.NodeList clauses)
            throws ParseException
    {
        CEAST node = new CEAST(CEAST.Sort.CONSTRAINT);
        node.clauses = clauses;
        node.dimdefs = dimdefs; // save for constructing the DMR
        this.constraint = node;
        return node;
    }

    @Override
    CEAST
    projection(CEAST segmenttree)
            throws ParseException
    {
        CEAST node = new CEAST(CEAST.Sort.PROJECTION);
        node.tree = segmenttree;
        return node;
    }

    @Override
    CEAST
    segmenttree(CEAST parent, CEAST segment)
    {
        if(parent == null)
            parent = segment;
        else {
            parent.addSegment(segment);
            parent.isleaf = false;
        }
        return parent;
    }

    @Override
    CEAST
    segmenttree(CEAST parent, CEAST.NodeList forest)
    {
        assert parent != null;
        parent.isleaf = false;
        for(CEAST node : forest) {
            parent.addSegment(node);
        }
        return parent;
    }

    @Override
    CEAST
    segment(String name, CEAST.SliceList slices)
            throws ParseException
    {
        CEAST node = new CEAST(CEAST.Sort.SEGMENT);
        node.name = name;
        node.slices = (slices == null ? new CEAST.SliceList() : slices);
        return node;
    }

    @Override
    Slice
    slice(CEAST.SliceList subslices)
            throws ParseException
    {
        Slice slice = null;
        if(subslices != null) {
            switch (subslices.size()) {
            case 0:
                break;
            case 1:
                slice = subslices.get(0); // no need for a multislice
                break;
            default:
                List<Slice> list = new ArrayList(subslices);
                try {
                    slice = new MultiSlice(list);
                } catch (DapException de) {
                    throw new ParseException(de);
                }
                break;
            }
        }
        return slice;
    }

    @Override
    Slice
    subslice(int state, String sfirst, String send, String sstride)
            throws ParseException
    {
        long first = 0;
        long last = UNDEFINED;
        long stop = UNDEFINED;
        long stride = 1;
        try {
            if(sfirst != null) first = Long.parseLong(sfirst);
            if(send != null) last = Long.parseLong(send);
            if(sstride != null) stride = Long.parseLong(sstride);
        } catch (NumberFormatException nfe) {
            throw new ParseException(String.format("Illegal slice: [%s:%s:%s]",
                    sfirst, send, sstride));
        }
        stop = (last == UNDEFINED ? UNDEFINED : (last+1));

        Slice x;
        try {
            x = new Slice(first, stop, stride);
        } catch (DapException de) {
            throw new ParseException(de);
        }

        try {
            // Fill in where possible
            switch (state) {
            case 0: // []
                x.setConstrained(false);
                break;
            case 1: // [i]
                x.setIndices(x.getFirst(), x.getFirst()+1, 1);
                break;
            case 2: // [f:l]
                x.setIndices(x.getFirst(), x.getStop(), 1);
                break;
            case 3: // [f:s:l]
            case 4: // [f:]
            case 5: // [f:s:]
                break;
            default:
                assert false : "Illegal slice case";
            }
        } catch (DapException de) {
            throw new ParseException(de);
        }
        return x;
    }

    @Override
    void
    dimredef(String name, Slice slice)
            throws ParseException
    {
        // First, make sure name is defined only once
        if(dimdefs.containsKey(name))
            throw new ParseException("Multiply defined shared dim: " + name);
        // Now locate the corresponding underlying shared dimension
        DapDimension dim;
        try {
            dim = (DapDimension) this.template.getDataset().findByFQN(name, DapSort.DIMENSION);
        } catch (DapException de) {
            throw new ParseException(de);
        }
        if(dim == null)
            throw new ParseException("Attempt to redefine a non-existent shared dimension: " + name);
        // Verify that the slice is consistent with the shared dimension
        try {
            slice.setMaxSize(dim.getSize());
            slice.finish();
        } catch (DapException de) {
            throw new ParseException(de);
        }
        if(slice.getLast() >= dim.getSize())
            throw new ParseException("Slice is inconsistent with the underlying shared dimension: " + name);
        dimdefs.put(name, slice);
    }

    //////////////////////////////////////////////////
    // Selection actions

    @Override
    CEAST
    selection(CEAST projection, CEAST filter)
            throws ParseException
    {
        CEAST node = new CEAST(CEAST.Sort.SELECTION);
        node.projection = projection;
        node.filter = filter;
        return node;
    }

    @Override
    CEAST
    logicalAnd(CEAST lhs, CEAST rhs)
            throws ParseException
    {
        CEAST node = new CEAST(CEAST.Sort.EXPR);
        node.op = CEAST.Operator.AND;
        node.lhs = lhs;
        node.rhs = rhs;
        return node;
    }

    @Override
    CEAST
    logicalNot(CEAST lhs)
            throws ParseException
    {
        CEAST node = new CEAST(CEAST.Sort.EXPR);
        node.op = CEAST.Operator.NOT;
        node.lhs = lhs;
        return node;
    }

    @Override
    CEAST
    predicate(CEAST.Operator op, CEAST lhs, CEAST rhs)
            throws ParseException
    {
        CEAST node = new CEAST(CEAST.Sort.EXPR);
        node.op = op;
        node.lhs = (CEAST) lhs;
        node.rhs = (CEAST) rhs;
        return node;
    }

    @Override
    CEAST
    predicaterange(CEAST.Operator op1, CEAST.Operator op2, CEAST lhs, CEAST mid, CEAST rhs)
            throws ParseException
    {
        CEAST node2 = new CEAST(CEAST.Sort.EXPR);
        node2.op = op2;
        node2.lhs = (CEAST) mid;
        node2.rhs = (CEAST) rhs;
        CEAST node1 = new CEAST(CEAST.Sort.EXPR);
        node1.op = op1;
        node1.lhs = (CEAST) lhs;
        node1.rhs = (CEAST) mid;
        CEAST andnode = new CEAST(CEAST.Sort.EXPR);
        andnode.op = CEAST.Operator.AND;
        andnode.lhs = node1;
        andnode.rhs = node2;
        return andnode;
    }

    @Override
    CEAST
    fieldname(String value)
            throws ParseException
    {
        CEAST seg = new CEAST(CEAST.Sort.SEGMENT);
        seg.name = value;
        seg.isleaf = true;
        return seg;
    }

    @Override
    CEAST
    constant(CEAST.Constant sort, String value)
            throws ParseException
    {
        CEAST con = new CEAST(CEAST.Sort.CONSTANT);
        con.kind = sort;
        switch (sort) {
        case STRING:
            con.value = value;
            break;
        case LONG:
            try {
                con.value = Long.parseLong(value);
            } catch (NumberFormatException nfe) {
                throw new ParseException(nfe);
            }
            break;
        case DOUBLE:
            try {
                con.value = Double.parseDouble(value);
            } catch (NumberFormatException nfe) {
                throw new ParseException(nfe);
            }
            break;
        case BOOLEAN:
            if(value.equals("0") || value.equalsIgnoreCase("false"))
                con.value = Boolean.FALSE;
            else if(value.equals("1") || value.equalsIgnoreCase("true"))
                con.value = Boolean.TRUE;
            else
                throw new ParseException("Malformed boolean constant: " + value);
            break;
        default:
            throw new ParseException("Unknown constant kind: " + sort);
        }
        return con;
    }

    //////////////////////////////////////////////////
    // Parser list support

    @Override
    CEAST.NodeList
    nodelist(CEAST.NodeList list, CEAST ast)
    {
        if(list == null) list = new CEAST.NodeList();
        if(ast != null) list.add(ast);
        return list;
    }

    @Override
    CEAST.SliceList
    slicelist(CEAST.SliceList list, Slice slice)
    {
        if(list == null) list = new CEAST.SliceList();
        if(slice != null) list.add(slice);
        return list;
    }

}


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

import ucar.nc2.time.CalendarDate;
import ucar.unidata.io.RandomAccessFile;

import javax.annotation.concurrent.Immutable;
import java.io.IOException;

/**
 * A class representing the IdentificationSection (section 1) of a BUFR record.
 * Handles editions 2,3,4.
 *
 * @author Robb Kambic
 * @author caron
 */
@Immutable
public class BufrIdentificationSection {
  static private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BufrIdentificationSection.class);
  static final private boolean warnDate = false;

  /**
   * Master Table number.
   */
  private final int master_table;

  /**
   * Identification of subcenter .
   */
  private final int subcenter_id;

  /**
   * Identification of center.
   */
  private final int center_id;

  /**
   * Update Sequence Number.
   */
  private final int update_sequence;

  /**
   * Optional section exists.
   */
  private final boolean hasOptionalSection;
  private final int optionalSectionLen;
  private final long optionalSectionPos;

  /**
   * Data category.
   */
  private final int category;

  /**
   * Data sub category.
   */
  private final int subCategory;

  private final int localSubCategory; // edition >= 4

  /**
   * Table Version numbers.
   */
  private final int master_table_version;
  private final int local_table_version;

  /**
   * Time of the obs (nominal)
   */
  private final int year, month, day, hour, minute, second;

  private final byte[] localUse;

  // *** constructors *******************************************************

  /**
   * Constructs a <tt>BufrIdentificationSection</tt> object from a raf.
   *
   * @param raf RandomAccessFile with Section 1 content
   * @param is  the BufrIndicatorSection, needed for the bufr edition number
   * @throws IOException if raf contains no valid BUFR file
   */
  public BufrIdentificationSection(RandomAccessFile raf, BufrIndicatorSection is) throws IOException {

    // section 1 octet 1-3 (length of section)
    int length = BufrNumbers.int3(raf);

    // master table octet 4
    master_table = raf.read();

    if (is.getBufrEdition() < 4) {
      if (length < 17) throw new IOException("Invalid BUFR message on "+raf.getLocation());

      if (is.getBufrEdition() == 2) {
        subcenter_id = 255;
        // Center  octet 5-6
        center_id = BufrNumbers.int2(raf);

      } else { // edition 3
        // Center  octet 5
        subcenter_id = raf.read();
        // Center  octet 6
        center_id = raf.read();
      }

      // Update sequence number  octet 7
      update_sequence = raf.read();

      // Optional section octet 8
      int optional = raf.read();
      hasOptionalSection = (optional & 0x80) != 0;

      // Category  octet 9
      category = raf.read();

      // Category  octet 10
      subCategory = raf.read();
      localSubCategory = -1; // not used

      // master table version octet 11
      master_table_version = raf.read();

      // local table version octet 12
      local_table_version = raf.read();

      // octets 13-17 (reference time of forecast)
      int lyear = raf.read();
      if (lyear > 100)
        lyear -= 100;
      year = lyear + 2000;
      int tempMonth = raf.read();
      month = (tempMonth == 0) ? 1 : tempMonth; // joda time does not allow 0 month
      int tempDay = raf.read();
      day = (tempDay == 0) ? 1 : tempDay;   // joda time does not allow 0 day
      hour = raf.read();
      minute = raf.read();
      second = 0;
      if (warnDate && (tempMonth == 0 || tempDay == 0)) {
        // From manual on codes
        // When accuracy of the time does not define a time unit, then the value for this unit shall be set to zero (e.g. for a
        // SYNOP observation at 09 UTC, minute = 0, second = 0.
        // NCEP codes their BUFR table messages with 0/0/0 0:0:0 in edition 3
        log.warn(raf.getLocation()+": month or day is zero, set to 1. {}/{}/{} {}:{}:{}", year, tempMonth, tempDay, hour, minute, second);
      }

      int n = length - 17;
      localUse = new byte[n];
      int nRead = raf.read(localUse);
      if (nRead != localUse.length)
          throw new IOException("Error reading BUFR local use field.");
    } else {  // BUFR Edition 4 and above are slightly different
      if (length < 22) throw new IOException("Invalid BUFR message");

      //    	 Center  octet 5 - 6
      center_id = BufrNumbers.int2(raf);

      // Sub Center  octet 7-8
      subcenter_id = BufrNumbers.int2(raf);

      //	    Update sequence number  octet 9
      update_sequence = raf.read();

      // Optional section octet 10
      int optional = raf.read();
      hasOptionalSection = (optional & 0x40) != 0;

      //	    Category  octet 11
      category = raf.read();

      // International Sub Category  octet 12
      subCategory = raf.read();

      // Local Sub Category Octet 13 - just read this for now
      localSubCategory = raf.read();

      //	    master table version octet 14
      master_table_version = raf.read();

      // local table version octet 15
      local_table_version = raf.read();
      // octets 16-22 (reference time of forecast)

      // Octet 16-17 is the 4-digit year
      year = BufrNumbers.int2(raf);
      month = raf.read();
      day = raf.read();
      hour = raf.read();
      minute = raf.read();
      second = raf.read();

      int n = length - 22;
      localUse = new byte[n];
      int nRead = raf.read(localUse);
      if (nRead != localUse.length)
          throw new IOException("Error reading BUFR local use field.");
    }

    // skip optional section, but store position so can read if caller wants it
    if (hasOptionalSection) {
      int optionalLen = BufrNumbers.int3(raf);
      if (optionalLen % 2 != 0) optionalLen++;
      optionalSectionLen = optionalLen - 4;
      raf.skipBytes(1);
      optionalSectionPos = raf.getFilePointer();
      raf.skipBytes(optionalSectionLen);

    } else {
      optionalSectionLen = -1;
      optionalSectionPos = -1;
    }
  }

  /**
   * Identification of center.
   *
   * @return center id as int
   */
  public final int getCenterId() {
    return center_id;
  }

  /**
   * Identification of subcenter.
   *
   * @return subcenter as int
   */
  public final int getSubCenterId() {
    return subcenter_id;
  }

  /**
   * Get update sequence.
   *
   * @return update_sequence
   */
  public final int getUpdateSequence() {
    return update_sequence;
  }

  /**
   * return record header time as a CalendarDate
   *
   * @return referenceTime
   */
  public final CalendarDate getReferenceTime() {
    int sec = (second < 0 || second > 59) ? 0 : second;
    return CalendarDate.of(null, year, month, day, hour, minute, sec);
  }

  public final int getCategory() {
    return category;
  }

  public final int getSubCategory() {
    return subCategory;
  }

  public final int getLocalSubCategory() {
    return localSubCategory;
  }

  public final int getMasterTableId() {
    return master_table;
  }

  public final int getMasterTableVersion() {
    return master_table_version;
  }

  public final int getLocalTableVersion() {
    return local_table_version;
  }

  /**
   * last bytes of the id section are "reserved for local use by ADP centers.
   *
   * @return local use bytes, if any.
   */
  public final byte[] getLocalUseBytes() {
    return localUse;
  }

  public final byte[] getOptiondsalSection(RandomAccessFile raf) throws
          IOException {
    if (!hasOptionalSection) return null;

    byte[] optionalSection = new byte[optionalSectionLen - 4];
    raf.seek(optionalSectionPos);
    int nRead = raf.read(optionalSection);
    if (nRead != optionalSection.length)
        log.warn("Error reading optional section -- expected " +
                optionalSection.length + " but read " + nRead);
    return optionalSection;
  }

}


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

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

/**
 * Describe
 *
 * @author caron
 * @since 5/31/2015
 */
public class ArrayStructureBBsection extends ArrayStructureBB {
  protected int[] orgRecnum;

  /**
   * Make a section of an ArrayStructureBB
   *
   * @param org the original
   * @param section of the whole
   * @return original, if section is empty or the same saze as the original, else a section of the original
   */
  static public ArrayStructureBB factory(ArrayStructureBB org, Section section) {
    if (section == null || section.computeSize() == org.getSize())
      return org;
    return new ArrayStructureBBsection(org.getStructureMembers(), org.getShape(), org.getByteBuffer(), section);
  }

  private ArrayStructureBBsection(StructureMembers members, int[] shape, ByteBuffer bbuffer, Section section) {
    super(members, shape, bbuffer, 0);
    int n = (int) section.computeSize();
    Section.Iterator iter = section.getIterator(shape);
    orgRecnum = new int[n];
    int count = 0;
    while (iter.hasNext()) {
      orgRecnum[count++] = iter.next(null);
    }
  }

  @Override
  protected int calcOffsetSetOrder(int recnum, StructureMembers.Member m) {
    if (null != m.getDataObject())
      bbuffer.order( (ByteOrder) m.getDataObject());
    return bb_offset + orgRecnum[recnum] * getStructureSize() + m.getDataParam();
  }
}


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

package thredds.ldm;

import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.TermEnum;
import org.apache.lucene.index.Term;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.DateTools;

import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Date;

/**
 * Class Description.
 *
 * @author caron
 * @since Aug 14, 2008
 */
public class LuceneIndexer {

  /**
   * Makes a document for a File.
   *

   * The document has three fields:
   *

   *
path--containing the pathname of the file, as a stored,
   * untokenized field;
   *
modified--containing the last modified date of the file as
   * a field as created by DateTools; and
   *
contents--containing the full contents of the file, as a
   * Reader field;
   */
  public Document makeDocument(File f)  throws java.io.FileNotFoundException {

    // make a new, empty document
    Document doc = new Document();

    // Add the path of the file as a field named "path".  Use a field that is
    // indexed (i.e. searchable), but don't tokenize the field into words.
    doc.add(new Field("path", f.getPath(), Field.Store.YES, Field.Index.UN_TOKENIZED));

    // Add the last modified date of the file a field named "modified".  Use
    // a field that is indexed (i.e. searchable), but don't tokenize the field
    // into words.
    doc.add(new Field("modified",
            DateTools.timeToString(f.lastModified(), DateTools.Resolution.MINUTE),
            Field.Store.YES, Field.Index.UN_TOKENIZED));

    // Add the contents of the file to a field named "contents".  Specify a Reader,
    // so that the text of the file is tokenized and indexed, but not stored.
    // Note that FileReader expects the file to be in the system's default encoding.
    // If that's not the case searching for special characters will fail.
    // doc.add(new Field("contents", new FileReader(f)));

    // return the document
    return doc;
  }

  void indexDocs(IndexWriter writer, File file) throws IOException {
    // do not try to index files that cannot be read
    if (file.canRead()) {
      if (file.isDirectory()) {
        String[] files = file.list();
        // an IO error could occur
        if (files != null) {
          for (int i = 0; i < files.length; i++) {
            indexDocs(writer, new File(file, files[i]));
          }
        }
      } else {
        System.out.println("adding " + file);
        try {
          writer.addDocument(makeDocument(file));
        }
        // at least on windows, some temporary files raise this exception with an "access denied" message
        // checking if the file can be read doesn't help
        catch (FileNotFoundException fnfe) {
          ;
        }
      }
    }
  }

  static final File INDEX_DIR = new File("D:/bufr/index/");
  static final File DOC_DIR = new File("D:/bufr/");

  /**
   * Index all text files under a directory.
   */
  public static void main1(String[] args) {

    if (INDEX_DIR.exists()) {
      System.out.println("Cannot save index to '" + INDEX_DIR + "' directory, please delete it first");
      System.exit(1);
    }

    LuceneIndexer indexer = new LuceneIndexer();
    Date start = new Date();
    try {
      IndexWriter writer = new IndexWriter(INDEX_DIR, new StandardAnalyzer(), true);
      System.out.println("Indexing to directory '" + INDEX_DIR + "'...");
      indexer.indexDocs(writer, DOC_DIR);
      System.out.println("Optimizing...");
      writer.optimize();
      writer.close();

      Date end = new Date();
      System.out.println(end.getTime() - start.getTime() + " total milliseconds");

    } catch (IOException e) {
      System.out.println(" caught a " + e.getClass() + "\n with message: " + e.getMessage());
    }
  }

    public static void main(String[] args) throws IOException {
      IndexReader reader = IndexReader.open(INDEX_DIR);

      System.out.println("Terms= ");
      TermEnum te = reader.terms();
      while (te.next()) {
        Term t = te.term();
        System.out.println(" term= "+t);
      }

      System.out.println("Documents= ");
      for (int docno=0; docno<reader.maxDoc(); docno++) {
        Document d = reader.document(docno);
        System.out.println(" doc= "+d);
      }

      reader.close();
    }

}


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

package ucar.nc2.grib.grib1;

import com.google.protobuf.ByteString;
import thredds.inventory.CollectionUpdateType;
import ucar.nc2.constants.CDM;
import ucar.nc2.grib.GribIndex;
import ucar.nc2.grib.GribIndexCache;
import ucar.nc2.stream.NcStream;
import ucar.unidata.io.RandomAccessFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;

/**
 * Read and Write Grib1 index (gbx9).
 * Hides Grib1IndexProto
 *
 * sample use:
 * <pre>
      Grib1Index index = new Grib1Index();
      if (!index.readIndex(path))
        index.makeIndex(path);

      for (Grib1SectionGridDefinition gds : index.getGds()) {
        if (gdsSet.get(gds.calcCRC()) == null)
          gdsSet.put(gds.calcCRC(), gds);
      }

      for (Grib1Record gr : index.getRecords()) {
        gr.setFile(fileno);

        Grib1Pds pds = gr.getPDSsection().getPDS();
        int discipline = gr.getDiscipline();

        int id = gr.cdmVariableHash();
        Grib1ParameterBean bean = pdsSet.get(id);
        if (bean == null) {
          bean = new Grib1ParameterBean(gr);
          pdsSet.put(id, bean);
          params.add(bean);
        }
        bean.addRecord(gr);
      }
      </pre>
 *
 * @author John
 * @since 9/5/11
 */
public class Grib1Index extends GribIndex {
  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Grib1Index.class);

  public static final String MAGIC_START = "Grib1Index";
  private static final int version = 5;
  private static final boolean debug = false;

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

  private List<Grib1SectionGridDefinition> gdsList;
  private List<Grib1Record> records;

  public List<Grib1SectionGridDefinition> getGds() {
    return gdsList;
  }

  public List<Grib1Record> getRecords() {
    return records;
  }

  @Override
  public int getNRecords() {
    return records.size();
  }

  public boolean readIndex(String filename, long gribLastModified) {
    return readIndex(filename, gribLastModified, CollectionUpdateType.test);
  }

  public boolean readIndex(String filename, long gribLastModified, CollectionUpdateType force) {
    String idxPath = filename;
    if (!idxPath.endsWith(GBX9_IDX)) idxPath += GBX9_IDX;
    File idxFile = GribIndexCache.getExistingFileOrCache(idxPath);
    if (idxFile == null) return false;

    long idxModified = idxFile.lastModified();
    if ((force != CollectionUpdateType.nocheck) && (idxModified < gribLastModified)) return false; // force new index if file was updated

    try (FileInputStream fin = new FileInputStream(idxFile)) {
      //// check header is ok
      if (!NcStream.readAndTest(fin, MAGIC_START.getBytes(CDM.utf8Charset))) {
        logger.info("Bad magic number of grib index, on file = {}", idxFile);
        return false;
      }

      int v = NcStream.readVInt(fin);
      if (v != version) {
        if ((v == 0) || (v > version))
          throw new IOException("Grib1Index found version " + v + ", want version " + version + " on " + filename);
        if (logger.isDebugEnabled()) logger.debug("Grib1Index found version " + v + ", want version " + version + " on " + filename);
        return false;
      }

      int size = NcStream.readVInt(fin);
      if (size <= 0 || size > 100 * 1000 * 1000) { // try to catch garbage
        logger.warn("Grib1Index bad size = {} for {} ", size, filename);
        return false;
      }

      byte[] m = new byte[size];
      NcStream.readFully(fin, m);

      Grib1IndexProto.Grib1Index proto = Grib1IndexProto.Grib1Index.parseFrom(m);
      logger.debug("%s for %s%n",  proto.getFilename(), filename);

      gdsList = new ArrayList<>(proto.getGdsListCount());
      for (Grib1IndexProto.Grib1GdsSection pgds : proto.getGdsListList()) {
        Grib1SectionGridDefinition gds = readGds(pgds);
        gdsList.add(gds);
      }
      logger.debug(" read %d gds%n", gdsList.size());

      records = new ArrayList<>(proto.getRecordsCount());
      for (Grib1IndexProto.Grib1Record precord : proto.getRecordsList()) {
        records.add(readRecord(precord));
      }
      logger.debug(" read %d records%n", records.size());

    } catch (NegativeArraySizeException | IOException e) {
      logger.error("GribIndex failed on " + filename, e);
      return false;
    }

    return true;
  }

  // deserialize the Grib1Record object
  private Grib1Record readRecord(Grib1IndexProto.Grib1Record p) {
    Grib1SectionIndicator is = new Grib1SectionIndicator(p.getGribMessageStart(), p.getGribMessageLength());
    Grib1SectionProductDefinition pds = new Grib1SectionProductDefinition(p.getPds().toByteArray());

    Grib1SectionGridDefinition gds = pds.gdsExists() ? gdsList.get(p.getGdsIdx()) : new Grib1SectionGridDefinition(pds);
    Grib1SectionBitMap bms = pds.bmsExists() ? new Grib1SectionBitMap(p.getBmsPos()) : null;

    Grib1SectionBinaryData dataSection = new Grib1SectionBinaryData(p.getDataPos(), p.getDataLen());
    return new Grib1Record(p.getHeader().toByteArray(), is, gds, pds, bms, dataSection);
  }

  private Grib1SectionGridDefinition readGds(Grib1IndexProto.Grib1GdsSection proto) {
    ByteString bytes = proto.getGds();
    return new Grib1SectionGridDefinition(bytes.toByteArray());
  }

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

  // LOOK what about extending an index ??
  public boolean makeIndex(String filename, RandomAccessFile dataRaf) throws IOException {
    String idxPath = filename;
    if (!idxPath.endsWith(GBX9_IDX)) idxPath += GBX9_IDX;
    File idxFile = GribIndexCache.getFileOrCache(idxPath);
    File idxFileTmp = GribIndexCache.getFileOrCache(idxPath + ".tmp");

    RandomAccessFile raf = null;
    try (FileOutputStream fout = new FileOutputStream(idxFileTmp)) {
      //// header message
      fout.write(MAGIC_START.getBytes(CDM.utf8Charset));
      NcStream.writeVInt(fout, version);

      Map<Long, Integer> gdsMap = new HashMap<>();
      gdsList = new ArrayList<>();
      records = new ArrayList<>(200);

      Grib1IndexProto.Grib1Index.Builder rootBuilder = Grib1IndexProto.Grib1Index.newBuilder();
      rootBuilder.setFilename(filename);

      if (dataRaf == null)  { // open if dataRaf not already open
        raf = RandomAccessFile.acquire(filename);
        dataRaf = raf;
      }

      Grib1RecordScanner scan = new Grib1RecordScanner(dataRaf);
      while (scan.hasNext()) {
        Grib1Record r = scan.next();
        if (r == null) break; // done
        records.add(r);

        Grib1SectionGridDefinition gdss = r.getGDSsection();
        Integer index = gdsMap.get(gdss.calcCRC());
        if (gdss.getPredefinedGridDefinition() >= 0) // skip predefined gds - they dont have raw bytes
          index = 0;
        else if (index == null) {
          gdsList.add(gdss);
          index = gdsList.size() - 1;
          gdsMap.put(gdss.calcCRC(), index);
          rootBuilder.addGdsList(makeGdsProto(gdss));
        }
        rootBuilder.addRecords(makeRecordProto(r, index));
      }

      if (records.isEmpty())
        throw new RuntimeException("No GRIB1 records found in "+dataRaf.getLocation());

      ucar.nc2.grib.grib1.Grib1IndexProto.Grib1Index index = rootBuilder.build();
      byte[] b = index.toByteArray();
      NcStream.writeVInt(fout, b.length); // message size
      fout.write(b);  // message  - all in one gulp
      logger.debug("  made gbx9 index for {} size={}", filename, b.length);
      return true;

    } finally {
      if (raf != null) raf.close();   // only close if it was opened here

            // now switch
      RandomAccessFile.eject(idxFile.getPath());
      boolean deleteOk = !idxFile.exists() || idxFile.delete();
      boolean renameOk = idxFileTmp.renameTo(idxFile);
      if (!deleteOk)
        logger.error("  could not delete Grib1Index= {}", idxFile.getPath());
      if (!renameOk)
        logger.error("  could not rename Grib1Index= {}", idxFile.getPath());
    }
  }

  private ucar.nc2.grib.grib1.Grib1IndexProto.Grib1Record makeRecordProto(Grib1Record r, int gdsIndex) {
    Grib1IndexProto.Grib1Record.Builder b = Grib1IndexProto.Grib1Record.newBuilder();

    b.setHeader(ByteString.copyFrom(r.getHeader()));

    b.setGribMessageStart(r.getIs().getStartPos());
    b.setGribMessageLength(r.getIs().getMessageLength());

    b.setGdsIdx(gdsIndex);
    Grib1SectionProductDefinition pds = r.getPDSsection();
    b.setPds(ByteString.copyFrom(pds.getRawBytes()));

    if (pds.bmsExists()) {
      Grib1SectionBitMap bms = r.getBitMapSection();
      b.setBmsPos(bms.getStartingPosition());
    }

    Grib1SectionBinaryData ds = r.getDataSection();
    b.setDataPos(ds.getStartingPosition());
    b.setDataLen(ds.getLength());

    return b.build();
  }

  private Grib1IndexProto.Grib1GdsSection makeGdsProto(Grib1SectionGridDefinition gds) {
    Grib1IndexProto.Grib1GdsSection.Builder b = Grib1IndexProto.Grib1GdsSection.newBuilder();
    b.setGds(ByteString.copyFrom(gds.getRawBytes()));
    return b.build();
  }

  public static void main(String[] args) throws IOException {
   String gribName = args[0];
   new Grib1Index().makeIndex(gribName, null);
  }

}


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

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;

import java.io.File;
import java.util.List;

/**
 * Implements the FileSource interface using a chain of DescendantFileSource
 * objects. This allows a relative path to be given and located in the first
 * DescendantFileSource that contains a matching File.
 *
 * @author edavis
 * @since 4.0
 */
public class ChainedFileSource implements FileSource {
  //private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger( ChainedFileSource.class );

  private final List<DescendantFileSource> chain;

  public ChainedFileSource(List<DescendantFileSource> chain) {
    if (chain == null || chain.isEmpty())
      throw new IllegalArgumentException("Locator chain must not be null or empty.");

    for (DescendantFileSource dfs : chain)
      if (dfs == null)
        throw new IllegalArgumentException("Locator chain must not contain null items.");
    this.chain = chain;
  }

  /**
   * This implementation requires a relative path. The relative path may
   * not start with "../" or once "normalized" start with "../". Here
   * "normalized" means that "./" and "path/.." segments are removed,
   * e.g., "dir1/../../dir2" once normalized would be "../dir2".
   *
   * @param path the relative path to the desired File.
   * @return the File represented by the given relative path or null if the path is null or the File it represents does not exist.
   */
  public File getFile(String path) {
    File file;
    for (DescendantFileSource curLocator : chain) {
      file = curLocator.getFile(path);
      if (file != null)
        return file;
    }
    return null;
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
            .add("chain", chain)
            .toString();
  }
}


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

package ucar.nc2.dataset.conv;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.constants.AxisType;
import ucar.nc2.util.CancelTask;
import ucar.nc2.dataset.*;
import ucar.unidata.geoloc.projection.*;
import ucar.unidata.geoloc.ProjectionRect;

import java.util.*;

/**
 * Models-3/EDSS Input/Output netcf format.
 *

 * The Models-3/EDSS Input/Output Applications Programming Interface (I/O API)
 * is the standard data access library for both NCSC's EDSS project and EPA's Models-3.
 *

 * 6/24/09: Modified to support multiple projection types of data by Qun He <qunhe@unc.edu> and Alexis Zubrow <azubrow@unc.edu>
 * added makePolarStereographicProjection, UTM, and modified latlon
 *

 * 09/2010 plessel.todd@epa.gov add projections 7,8,9,10
 *
 * @author plessel.todd@epa.gov
 * @author caron
 * @see http://www.baronams.com/products/ioapi/index.html
 */

public class M3IOConvention extends CoordSysBuilder {
   private static final double earthRadius = 6370.000; // km
  /**
   * Do we think this is a M3IO file.
   *
   * @param ncfile the NetcdfFile to test
   * @return true if we think this is a M3IO file.
   */
  public static boolean isMine(NetcdfFile ncfile) {
    return (null != ncfile.findGlobalAttribute("XORIG"))
            && (null != ncfile.findGlobalAttribute("YORIG"))
            && (null != ncfile.findGlobalAttribute("XCELL"))
            && (null != ncfile.findGlobalAttribute("YCELL"))
            && (null != ncfile.findGlobalAttribute("NCOLS"))
            && (null != ncfile.findGlobalAttribute("NROWS"));

    // M3IOVGGridConvention - is this true for this class ??
    // return ncFile.findGlobalAttribute( "VGLVLS" ) != null && isValidM3IOFile_( ncFile );

  }

  public M3IOConvention() {
    this.conventionName = "M3IO";
  }

  public void augmentDataset(NetcdfDataset ncd, CancelTask cancelTask) {
    if (null != ncd.findVariable("x")) return; // check if its already been done - aggregating enhanced datasets.
    if (null != ncd.findVariable("lon")) return; // check if its already been done - aggregating enhanced datasets.

    constructCoordAxes(ncd);
    ncd.finish();
  }

  private CoordinateTransform ct = null;

  protected void constructCoordAxes(NetcdfDataset ds) {

    Dimension dimx = ds.findDimension("COL");
    int nx = dimx.getLength();

    Dimension dimy = ds.findDimension("ROW");
    int ny = dimy.getLength();

    int projType = findAttributeInt(ds, "GDTYP");
    boolean isLatLon = (projType == 1);
    if (isLatLon) {
      //ds.addCoordinateAxis(  makeCoordAxis( ds, "lon", "COL", nx, "XORIG", "XCELL", "degrees east"));
      //ds.addCoordinateAxis(  makeCoordAxis( ds, "lat", "ROW", ny, "YORIG", "YCELL", "degrees north"));

      ds.addCoordinateAxis(makeCoordLLAxis(ds, "lon", "COL", nx, "XORIG", "XCELL", "degrees east"));
      ds.addCoordinateAxis(makeCoordLLAxis(ds, "lat", "ROW", ny, "YORIG", "YCELL", "degrees north"));
      ct = makeLatLongProjection(ds);

      VariableDS v = makeCoordinateTransformVariable(ds, ct);
      ds.addVariable(null, v);
      v.addAttribute(new Attribute(_Coordinate.Axes, "lon lat"));

    } else {
      ds.addCoordinateAxis(makeCoordAxis(ds, "x", "COL", nx, "XORIG", "XCELL", "km"));
      ds.addCoordinateAxis(makeCoordAxis(ds, "y", "ROW", ny, "YORIG", "YCELL", "km"));

      if (projType == 2)
        ct = makeLCProjection(ds);
      else if (projType == 3)
        ct = makeTMProjection(ds);
      else if (projType == 4)
        ct = makeSTProjection(ds);
      else if (projType == 5)
        ct = makeUTMProjection(ds);
      else if (projType == 6) // Was 7. See http://www.baronams.com/products/ioapi/GRIDS.html
        ct = makePolarStereographicProjection(ds);
      else if (projType == 7)
        ct = makeEquitorialMercatorProjection(ds);
      else if (projType == 8)
        ct = makeTransverseMercatorProjection(ds);
      else if (projType == 9)
        ct = makeAlbersProjection(ds);
      else if (projType == 10)
        ct = makeLambertAzimuthalProjection(ds);

      if (ct != null) {
        VariableDS v = makeCoordinateTransformVariable(ds, ct);
        ds.addVariable(null, v);
        v.addAttribute(new Attribute(_Coordinate.Axes, "x y"));
      }
    }

    makeZCoordAxis(ds, "LAY", "VGLVLS", "sigma");
    makeTimeCoordAxis(ds, "TSTEP");
  }

  private CoordinateAxis makeCoordAxis(NetcdfDataset ds, String name, String dimName, int n,
                                       String startName, String incrName, String unitName) {

    double start = .001 * findAttributeDouble(ds, startName); // km
    double incr = .001 * findAttributeDouble(ds, incrName); // km
    start = start + incr / 2.; // shifting x and y to central
    CoordinateAxis v = new CoordinateAxis1D(ds, null, name, DataType.DOUBLE, dimName, unitName,
            "synthesized coordinate from " + startName + " " + incrName + " global attributes");
    v.setValues(n, start, incr);
    return v;
  }

  private CoordinateAxis makeCoordLLAxis(NetcdfDataset ds, String name, String dimName, int n,
                                         String startName, String incrName, String unitName) {
    // Makes coordinate axes for Lat/Lon
    double start = findAttributeDouble(ds, startName); // degrees
    double incr = findAttributeDouble(ds, incrName); // degrees
    // The coordinate value should be the cell center.
    // I recommend also adding a bounds coordinate variable for clarity in the future.
    start = start + incr / 2.; // shiftin lon and lat to central
    CoordinateAxis v = new CoordinateAxis1D(ds, null, name, DataType.DOUBLE, dimName, unitName,
            "synthesized coordinate from " + startName + " " + incrName + " global attributes");
    v.setValues(n, start, incr);
    return v;
  }

  private void makeZCoordAxis(NetcdfDataset ds, String dimName, String levelsName, String unitName) {
    Dimension dimz = ds.findDimension(dimName);
    int nz = dimz.getLength();
    ArrayDouble.D1 dataLev = new ArrayDouble.D1(nz);
    ArrayDouble.D1 dataLayers = new ArrayDouble.D1(nz + 1);

    // layer values are a numeric global attribute array !!
    Attribute layers = ds.findGlobalAttribute("VGLVLS");
    for (int i = 0; i <= nz; i++)
      dataLayers.set(i, layers.getNumericValue(i).doubleValue());

    for (int i = 0; i < nz; i++) {
      double midpoint = (dataLayers.get(i) + dataLayers.get(i + 1)) / 2;
      dataLev.set(i, midpoint);
    }

    CoordinateAxis v = new CoordinateAxis1D(ds, null, "level", DataType.DOUBLE, dimName, unitName,
            "synthesized coordinate from " + levelsName + " global attributes");
    v.setCachedData(dataLev, true);
    v.addAttribute(new Attribute("positive", "down"));
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.GeoZ.toString()));

    // layer edges
    String edge_name = "layer";
    Dimension lay_edge = new Dimension(edge_name, nz + 1);

    ds.addDimension(null, lay_edge);
    CoordinateAxis vedge = new CoordinateAxis1D(ds, null, edge_name, DataType.DOUBLE, edge_name, unitName,
            "synthesized coordinate from " + levelsName + " global attributes");
    vedge.setCachedData(dataLayers, true);
    v.setBoundaryRef(edge_name);

    ds.addCoordinateAxis(v);
    ds.addCoordinateAxis(vedge);
  }

  private void makeTimeCoordAxis(NetcdfDataset ds, String timeName) {
    int start_date = findAttributeInt(ds, "SDATE");
    int start_time = findAttributeInt(ds, "STIME");
    int time_step = findAttributeInt(ds, "TSTEP");

    int year = start_date / 1000;
    int doy = start_date % 1000;
    int hour = start_time / 10000;
    start_time = start_time % 10000;
    int min = start_time / 100;
    int sec = start_time % 100;

    Calendar cal = new GregorianCalendar(new SimpleTimeZone(0, "GMT"));
    cal.clear();
    cal.set(Calendar.YEAR, year);
    cal.set(Calendar.DAY_OF_YEAR, doy);
    cal.set(Calendar.HOUR_OF_DAY, hour);
    cal.set(Calendar.MINUTE, min);
    cal.set(Calendar.SECOND, sec);
    //cal.setTimeZone( new SimpleTimeZone(0, "GMT"));

    java.text.SimpleDateFormat dateFormatOut = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    dateFormatOut.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));

    String units = "seconds since " + dateFormatOut.format(cal.getTime()) + " UTC";

    // parse the time step
    hour = time_step / 10000;
    time_step = time_step % 10000;
    min = time_step / 100;
    sec = time_step % 100;
    time_step = hour * 3600 + min * 60 + sec;

    Dimension dimt = ds.findDimension(timeName);
    int nt = dimt.getLength();
    ArrayInt.D1 data = new ArrayInt.D1(nt, false);
    for (int i = 0; i < nt; i++) {
      data.set(i, i * time_step);
    }

    // create the coord axis
    CoordinateAxis1D timeCoord = new CoordinateAxis1D(ds, null, "time", DataType.INT, timeName, units,
            "synthesized time coordinate from SDATE, STIME, STEP global attributes");
    timeCoord.setCachedData(data, true);
    timeCoord.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Time.toString()));

    ds.addCoordinateAxis(timeCoord);
  }

  private CoordinateTransform makeLatLongProjection(NetcdfDataset ds) {
    //double lat0 = findAttributeDouble(ds,  "YCENT");

    // Get lower left and upper right corners of domain in lat/lon
    double x1 = findAttributeDouble(ds, "XORIG");
    double y1 = findAttributeDouble(ds, "YORIG");
    double x2 = x1 + findAttributeDouble(ds, "XCELL") * findAttributeDouble(ds, "NCOLS");
    double y2 = y1 + findAttributeDouble(ds, "YCELL") * findAttributeDouble(ds, "NROWS");

    LatLonProjection ll = new LatLonProjection("LatitudeLongitudeProjection", new ProjectionRect(x1, y1, x2, y2));
    return new ProjectionCT("LatitudeLongitudeProjection", "FGDC", ll);
  }

  private CoordinateTransform makeLCProjection(NetcdfDataset ds) {
    double par1 = findAttributeDouble(ds, "P_ALP");
    double par2 = findAttributeDouble(ds, "P_BET");
    double lon0 = findAttributeDouble(ds, "XCENT");
    double lat0 = findAttributeDouble(ds, "YCENT");

    LambertConformal lc = new LambertConformal(lat0, lon0, par1, par2, 0.0, 0.0, earthRadius);

    return new ProjectionCT("LambertConformalProjection", "FGDC", lc);
  }

  private CoordinateTransform makePolarStereographicProjection(NetcdfDataset ds) {
    //boolean n_polar = (findAttributeDouble(ds, "P_ALP") == 1.0);
    //double central_meridian = findAttributeDouble(ds, "P_GAM");
    double lon0 = findAttributeDouble(ds, "XCENT");
    double lat0 = findAttributeDouble(ds, "YCENT");
    double latts = findAttributeDouble(ds, "P_BET");

    Stereographic sg = new Stereographic(latts, lat0, lon0, 0.0, 0.0, earthRadius);
    //sg.setCentralMeridian(centeral_meridian);

    return new ProjectionCT("PolarStereographic", "FGDC", sg);
  }

  private CoordinateTransform makeEquitorialMercatorProjection(NetcdfDataset ds) {
    double lon0 = findAttributeDouble(ds, "XCENT");
    double par  = findAttributeDouble(ds, "P_ALP");

    Mercator p = new Mercator(lon0, par, 0.0, 0.0, earthRadius);

    return new ProjectionCT("EquitorialMercator", "FGDC", p);
  }

  private CoordinateTransform makeTransverseMercatorProjection(NetcdfDataset ds) {
    double lat0 = findAttributeDouble(ds, "P_ALP");
    double tangentLon = findAttributeDouble(ds, "P_GAM");

    TransverseMercator p = new TransverseMercator(lat0, tangentLon, 1.0, 0.0, 0.0, earthRadius);

    return new ProjectionCT("TransverseMercator", "FGDC", p);
  }

  private CoordinateTransform makeAlbersProjection(NetcdfDataset ds) {
    double lat0 = findAttributeDouble(ds, "YCENT");
    double lon0 = findAttributeDouble(ds, "XCENT");
    double par1 = findAttributeDouble(ds, "P_ALP");
    double par2 = findAttributeDouble(ds, "P_BET");

    AlbersEqualArea p = new AlbersEqualArea(lat0, lon0, par1, par2, 0.0, 0.0, earthRadius);

    return new ProjectionCT("Albers", "FGDC", p);
  }

  private CoordinateTransform makeLambertAzimuthalProjection(NetcdfDataset ds) {
    double lat0 = findAttributeDouble(ds, "YCENT");
    double lon0 = findAttributeDouble(ds, "XCENT");

    LambertAzimuthalEqualArea p =
      new LambertAzimuthalEqualArea(lat0, lon0, 0.0, 0.0, earthRadius);

    return new ProjectionCT("LambertAzimuthal", "FGDC", p);
  }

  private CoordinateTransform makeSTProjection(NetcdfDataset ds) {
    double latt = findAttributeDouble(ds, "PROJ_ALPHA");
    if (Double.isNaN(latt))
      latt = findAttributeDouble(ds, "P_ALP");

    double lont = findAttributeDouble(ds, "PROJ_BETA");
    if (Double.isNaN(lont))
      lont = findAttributeDouble(ds, "P_BET");

    /**
     * Construct a Stereographic Projection.
     * @param latt tangent point of projection, also origin of projecion coord system
     * @param lont tangent point of projection, also origin of projecion coord system
     * @param scale scale factor at tangent point, "normally 1.0 but may be reduced"
     */
    Stereographic st = new Stereographic(latt, lont, 1.0, 0.0, 0.0, earthRadius);

    return new ProjectionCT("StereographicProjection", "FGDC", st);
  }

  private CoordinateTransform makeTMProjection(NetcdfDataset ds) {
    double lat0 = findAttributeDouble(ds, "PROJ_ALPHA");
    if (Double.isNaN(lat0))
      lat0 = findAttributeDouble(ds, "P_ALP");

    double tangentLon = findAttributeDouble(ds, "PROJ_BETA");
    if (Double.isNaN(tangentLon))
      tangentLon = findAttributeDouble(ds, "P_BET");

    /**
     * Construct a TransverseMercator Projection.
     * @param lat0 origin of projection coord system is at (lat0, tangentLon)
     * @param tangentLon longitude that the cylinder is tangent at ("central meridian")
     * @param scale scale factor along the central meridian
     */
    TransverseMercator tm = new TransverseMercator(lat0, tangentLon, 1.0, 0.0, 0.0, earthRadius);

    return new ProjectionCT("MercatorProjection", "FGDC", tm);
  }

  /**
   * Intend to use EPSG system parameters
   *
   * @param ds dataset
   * @return CoordinateTransform
   */
  private CoordinateTransform makeUTMProjection(NetcdfDataset ds) {
    int zone = (int) findAttributeDouble(ds, "P_ALP");
    double ycent = findAttributeDouble(ds, "YCENT");
    //double lon0 = findAttributeDouble( "X_CENT");
    //double lat0 = findAttributeDouble( "Y_CENT");

    /**
     * Construct a UTM Projection.
     * @param zone - UTM zone
     * @param if ycent < 0, then isNorth = False
     */
    boolean isNorth = true;
    if (ycent < 0)
      isNorth = false;
    UtmProjection utm = new UtmProjection(zone, isNorth);

    return new ProjectionCT("UTM", "EPSG", utm);
  }

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

  protected AxisType getAxisType(NetcdfDataset ds, VariableEnhanced ve) {
    Variable v = (Variable) ve;
    String vname = v.getShortName();

    if (vname.equalsIgnoreCase("x"))
      return AxisType.GeoX;

    if (vname.equalsIgnoreCase("y"))
      return AxisType.GeoY;

    if (vname.equalsIgnoreCase("lat"))
      return AxisType.Lat;

    if (vname.equalsIgnoreCase("lon"))
      return AxisType.Lon;

    if (vname.equalsIgnoreCase("time"))
      return AxisType.Time;

    if (vname.equalsIgnoreCase("level"))
      return AxisType.GeoZ;

    return null;
  }

  protected void makeCoordinateTransforms(NetcdfDataset ds) {
    if (ct != null) {
      VarProcess vp = findVarProcess(ct.getName(), null);
      if (vp != null)
        vp.ct = ct;
    }
    super.makeCoordinateTransforms(ds);
  }

  private double findAttributeDouble(NetcdfDataset ds, String attname) {
    Attribute att = ds.findGlobalAttributeIgnoreCase(attname);
    if ((att == null) || (att.isString())) return Double.NaN;
    return att.getNumericValue().doubleValue();
  }

  private int findAttributeInt(NetcdfDataset ds, String attname) {
    Attribute att = ds.findGlobalAttributeIgnoreCase(attname);
    return att.getNumericValue().intValue();
  }

}


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

package ucar.nc2.iosp.mcidas;

import edu.wisc.ssec.mcidas.AreaFile;
import ucar.ma2.Array;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Section;

import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;

import ucar.nc2.iosp.AbstractIOServiceProvider;

import ucar.nc2.util.CancelTask;

import ucar.unidata.io.RandomAccessFile;

import java.io.IOException;

/**
 * IOServiceProvider for McIDAS AREA files
 *
 * @author dmurray
 */
public class AreaServiceProvider extends AbstractIOServiceProvider {
  /**
   * AREA file reader
   */
  protected AreaReader areaReader;

  /**
   * Is this a valid file?
   *
   * @param raf RandomAccessFile to check
   * @return true if a valid McIDAS AREA file
   * @throws IOException problem reading file
   */
  public boolean isValidFile(RandomAccessFile raf) throws IOException {
    return AreaReader.isValidFile(raf);
  }

  public String getFileTypeId() {
    return "McIDASArea";
  }

  public String getFileTypeDescription() {
    return "McIDAS area file";
  }

  /**
   * Open the service provider for reading.
   *
   * @param raf        file to read from
   * @param ncfile     netCDF file we are writing to (memory)
   * @param cancelTask task for cancelling
   * @throws IOException problem reading file
   */
  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    super.open(raf, ncfile, cancelTask);

    if (areaReader == null)
      areaReader = new AreaReader();

    try {
      areaReader.init(raf.getLocation(), ncfile);

    } catch (Throwable e) {
      close();              // try not to leak files
      throw new IOException(e);

    } finally {
      raf.close(); // avoid leaks
    }

  }

  /**
   * Read the data for the variable
   *
   * @param v2      Variable to read
   * @param section section information
   * @return Array of data
   * @throws IOException           problem reading from file
   * @throws InvalidRangeException invalid Range
   */
  public Array readData(Variable v2, Section section) throws IOException, InvalidRangeException {
    return areaReader.readVariable(v2, section);
  }

  public void close() throws IOException {
    if (areaReader != null) {
      if (areaReader.af != null)
        areaReader.af.close();
      areaReader = null;
    }
  }

  // release any resources like file handles
  public void release() throws IOException {
    if (areaReader.af != null)
      areaReader.af.close();
  }

  // reacquire any resources like file handles
  public void reacquire() throws IOException {
    try {
      areaReader.af = new AreaFile(location);
    } catch (Throwable e) {
      throw new IOException(e);
    }
  }

}


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

package ucar.nc2.units;

import java.text.SimpleDateFormat;
import java.util.Date;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateFormatter;
import ucar.nc2.time.CalendarPeriod;

/**
 * Implements the thredds "dateType" and "dateTypeFormatted" XML element types.
 * This is mostly a general way to specify dates in a string.
 * It allows a date to mean "present". "Present" always sorts after any date, including dates in the future.
 * It allows an optional attribute called "type" which is an enumeration like "created", "modified", etc
 * taken from Dublin Core vocabulary.
 *

 * A DateType can be specified in the following ways:
 *

 *
 an xsd:date, with form "CCYY-MM-DD"
 *
 an xsd:dateTime with form "CCYY-MM-DDThh:mm:ss"
 *
 a valid udunits date string
 *
 the string "present"
 *

 *
 * @author john caron
 * @see THREDDS dateType
 */

public class DateType {
  private String text, format, type;
  private boolean isPresent, isBlank;
  private final CalendarDate date;

  /**
   * Constructor using a java.util.Date
   *
   * @param isPresent if true, this represents the "present time"
   * @param date      the given Date
   */
  public DateType(boolean isPresent, java.util.Date date) {
    this.isPresent = isPresent;
    this.date = isPresent ? null : CalendarDate.of(date);
  }

  /**
   * Constructor using a java.util.CalendarDate
   *
   * @param date the given CalendarDate
   */
  public DateType(CalendarDate date) {
    this.isPresent = false;
    this.date = date;
  }

  /**
   * no argument constructor for beans
   */
  public DateType() {
    isBlank = true;
    date = null;
  }

  /**
   * copy constructor
   *
   * @param src copy from here
   */
  public DateType(DateType src) {
    text = src.getText();
    format = src.getFormat();
    type = src.getType();
    isPresent = src.isPresent();
    isBlank = src.isBlank();
    date = src.getCalendarDate();
  }

  /**
   * Constructor.
   *
   * @param text   string representation
   * @param format using java.text.SimpleDateFormat, or null
   * @param type   type of date, or null
   * @throws java.text.ParseException if error parsing text
   */
  public DateType(String text, String format, String type) throws java.text.ParseException {

    text = (text == null) ? "" : text.trim();
    this.text = text;
    this.format = format;
    this.type = type;

    // see if its blank
    if (text.length() == 0) {
      isBlank = true;
      date = null;
      return;
    }

    // see if its the string "present"
    isPresent = text.equalsIgnoreCase("present");
    if (isPresent) {
      this.date = null;
      return;
    }

    // see if its got a format
    if (format != null) {
      SimpleDateFormat dateFormat = new java.text.SimpleDateFormat(format);
      Date d = dateFormat.parse(text);
      date = CalendarDate.of(d);
      return;
    }

    // see if its a udunits string
    if (text.indexOf("since") > 0) {
      date = CalendarDate.parseUdunits(null, text);
      if (date == null)
        throw new java.text.ParseException("invalid udunit date unit ="+text, 0);
      return;
    }

    date = CalendarDate.parseISOformat(null, text);
    if (date == null)
      throw new java.text.ParseException("invalid ISO date unit ="+text, 0);
  }

  /**
   * Constructor.
   *
   * @param text   string representation
   * @param format using java.text.SimpleDateFormat, or null
   * @param type   type of date, or null
   * @param cal2   ucar.nc2.time.Calendar of date, or null
   * @throws java.text.ParseException if error parsing text
   */
  public DateType(String text, String format, String type, ucar.nc2.time.Calendar cal2) throws java.text.ParseException {

	  if( cal2 == null) cal2 = ucar.nc2.time.Calendar.getDefault();

    text = (text == null) ? "" : text.trim();
    this.text = text;
    this.format = format;
    this.type = type;

    // see if its blank
    if (text.length() == 0) {
      isBlank = true;
      date = null;
      return;
    }

    // see if its the string "present"
    isPresent = text.equalsIgnoreCase("present");
    if (isPresent) {
      date = null;
      return;
    }

    // see if its got a format
    if (format != null) {
      SimpleDateFormat dateFormat = new java.text.SimpleDateFormat(format);
      java.util.Calendar  c =  java.util.Calendar.getInstance();
      c.setTime(dateFormat.parse(text));
      date = CalendarDate.of(cal2, c.getTimeInMillis());
      return;
    }

    // see if its a udunits string
    String calName = (cal2 == null) ? null : cal2.name();
    if (text.indexOf("since") > 0) {
      date = CalendarDate.parseUdunits(calName, text);
      if (date == null)
        throw new java.text.ParseException("invalid udunit date unit ="+text, 0);
      return;
    }

    date = CalendarDate.parseISOformat(calName, text);
    if (date == null)
      throw new java.text.ParseException("invalid ISO date unit ="+text, 0);
  }

  /**
   * Get this as a Date.
   * Does not handle non-standard Calendars.
   * @deprecated use getCalendarDate()
   * @return Date
   */
  public Date getDate() {
    return isPresent() ? new Date() : date.toDate();
  }

  /**
   * Get this as a CalendarDate
   *
   * @return CalendarDate
   */
  public CalendarDate getCalendarDate() {
    return isPresent() ? CalendarDate.present() : date;
  }

  /**
   * Does this represent the present time.
   *
   * @return true if present time.
   */
  public boolean isPresent() {
    return isPresent;
  }

  /**
   * Was blank text passed to the constructor.
   *
   * @return true if blank text passed to the constructor.
   */
  public boolean isBlank() {
    return isBlank;
  }

  /**
   * Get a text representation.
   *
   * @return text representation
   */
  public String getText() {
    if (isPresent) text = "present";
    if (text == null) text = toDateTimeString();
    return text;
  }

  /**
   * Get the SimpleDateFormat format for parsing the text.
   *
   * @return SimpleDateFormat format, or null
   */
  public String getFormat() {
    return format;
  }

  /**
   * Get the type of Date.
   *
   * @return type of Date, or null
   */
  public String getType() {
    return type;
  }

  /**
   * Set the type of Date.
   *
   * @param type type of Date
   */
  public DateType setType(String type) {
    this.type = type;
    return this;
  }

  /**
   * Is this date before the given date. if isPresent, always false.
   *
   * @param d test against this date
   * @return true if this date before the given date
   */
  public boolean before(Date d) {
    if (isPresent()) return false;
    return date.isBefore(CalendarDate.of(d));
  }

  /**
   * Is this date before the given date. if d.isPresent, always true, else if this.isPresent, false.
   *
   * @param d test against this date
   * @return true if this date before the given date
   */
  public boolean before(DateType d) {
    if (d.isPresent()) return true;
    if (isPresent()) return false;
    return date.isBefore(d.getCalendarDate());
  }

  /**
   * Is this date after the given date. if isPresent, always true.
   *
   * @param d test against this date
   * @return true if this date after the given date
   */
  public boolean after(Date d) {
    if (isPresent()) return true;
    return date.isAfter(CalendarDate.of(d));
  }

  /**
   * Same as DateFormatter.toDateOnlyString()
   *
   * @return formatted date
   */
  public String toDateString() {
    if (isPresent())
      return CalendarDateFormatter.toDateStringPresent();
    else
      return CalendarDateFormatter.toDateString(date);
  }

  /**
   * Same as CalendarDateFormatter.toDateTimeStringISO
   *
   * @return formatted date
   */
  public String toDateTimeString() {
   if (isPresent())
      return CalendarDateFormatter.toDateTimeStringISO(new Date());
    else
      return CalendarDateFormatter.toDateTimeStringISO(date);
  }

  /**
   * Get ISO formatted string
   *
   * @return ISO formatted date
   */
  public String toDateTimeStringISO() {
    return toDateTimeString();
  }

  /**
   * String representation
   *
   * @return getText()
   */
  public String toString() {
    return getText();
  }

  public int hashCode() {
    if (isBlank()) return 0;
    if (isPresent()) return 1;
    return getDate().hashCode();
  }

  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof DateType)) return false;
    DateType oo = (DateType) o;
    if (isPresent() && oo.isPresent()) return true;
    if (isBlank() && oo.isBlank()) return true;
    return oo.getDate().equals(getDate());
  }

  //private java.util.Calendar cal = null;

  public DateType add(TimeDuration d) {
    return add(d.getTimeUnit());
  }

  public DateType add(TimeUnit d) {
    CalendarDate useDate = getCalendarDate();
    CalendarDate result = useDate.add((int) d.getValueInSeconds(), CalendarPeriod.Field.Second);
    return new DateType(result);
  }

  public DateType subtract(TimeDuration d) {
    return subtract(d.getTimeUnit());
  }

  public DateType subtract(TimeUnit d) {
    CalendarDate useDate = getCalendarDate();
    CalendarDate result = useDate.add((int) -d.getValueInSeconds(), CalendarPeriod.Field.Second);
    return new DateType(result);
  }

  ////////////////////////////////////////////
  // test
  private static void doOne(String s) {
    try {
      System.out.println("\nString = (" + s + ")");
      DateType d = new DateType(s, null, null);
      System.out.println("DateType = (" + d.toString() + ")");
      System.out.println("Date = (" + d.getDate() + ")");
    } catch (java.text.ParseException e) {
      e.printStackTrace();
    }
  }

  /**
   * test
   */
  public static void main(String[] args) {
    doOne("T00:00:00Z");
  }

}


/* Copyright */
package thredds.server.catalog;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import thredds.client.catalog.*;
import thredds.client.catalog.builder.CatalogBuilder;
import thredds.client.catalog.builder.CatalogRefBuilder;
import thredds.client.catalog.builder.DatasetBuilder;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;

/**
 * Describe
 *
 * @author caron
 * @since 6/9/2015
 */
public class CatalogScan extends CatalogRef {
  static private final Logger logger = LoggerFactory.getLogger(CatalogScan.class);
  static public final String CATSCAN = "catalogScan.xml";

  private final String path, location, watch;

  public String getPath() {
    return path;
  }

  public String getLocation() {
    return location;
  } // reletive to ${tds.content.root.path}

  public String getWatch() {
    return watch;
  }

  public CatalogScan(DatasetNode parent, String name, String xlink, Map<String, Object> flds, String path, String location, String watch) {
    super(parent, name, xlink, flds, null, null);

    this.path = path;           // could use flds
    this.location = location;
    this.watch = watch;
  }

  // when we have a real catalog  (filename != CATSCAN)
  public ConfigCatalog getCatalog(File baseDir, String matchRemaining, String filename, CatalogReader reader) throws IOException {
    String relLocation = (matchRemaining.length() >= 1) ? location + "/" + matchRemaining : location;
    File absLocation = new File(baseDir, relLocation);
    ConfigCatalog cc = reader.getFromAbsolutePath(absLocation + "/" + filename);
    if (cc == null)
      logger.warn("Cant find catalog from scan: " + absLocation + "/" + filename);
    return cc;
  }

  // when we have a catalog built from a directory (filename == CATSCAN)
  public CatalogBuilder makeCatalogFromDirectory(File baseDir, String matchRemaining, URI baseURI) throws IOException {
    String relLocation = (matchRemaining.length() >= 1) ? location + "/" + matchRemaining : location;
    String name = (matchRemaining.length() >= 1) ? getName() + "/" + matchRemaining : getName();
    File absLocation = new File( baseDir, relLocation);

    // it must be a directory
    Path wantDir = absLocation.toPath();
    if (!Files.exists(wantDir)) throw new FileNotFoundException("Requested catalog does not exist =" + absLocation);
    if (!Files.isDirectory(wantDir)) throw new FileNotFoundException("Not a directory =" + absLocation);

    // Setup and create catalog builder.
    CatalogBuilder catBuilder = new CatalogBuilder();
    catBuilder.setBaseURI(baseURI);
    assert this.getParentCatalog() != null;

    DatasetBuilder top = new DatasetBuilder(null);
    top.transferMetadata(this, true);
    top.setName(name);
    top.put(Dataset.Id, null); // no id for top
    catBuilder.addDataset(top);

    // first look for catalogs
    try (DirectoryStream<Path> ds = Files.newDirectoryStream(wantDir, "*.xml")) {
      for (Path p : ds) {
        if (!Files.isDirectory(p)) {
          String pfilename = p.getFileName().toString();
          String urlPath = pfilename;

          //String path = dataDirComplete.length() == 0 ? filename : dataDirComplete + "/" + filename;  // reletive starting from current directory
          CatalogRefBuilder catref = new CatalogRefBuilder(top);
          catref.setTitle(urlPath);
          catref.setHref(urlPath);
          top.addDataset(catref);
        }
      }
    }

    // now look for directories
    try (DirectoryStream<Path> ds = Files.newDirectoryStream(wantDir)) {
      for (Path dir : ds) {
        if (Files.isDirectory(dir)) {
          String dfilename = dir.getFileName().toString();
          String urlPath = (matchRemaining.length() >= 1) ? dfilename + "/" + matchRemaining : dfilename;

          CatalogRefBuilder catref = new CatalogRefBuilder(top);
          catref.setTitle(urlPath);
          catref.setHref(urlPath + "/"+ CATSCAN);
          catref.addToList(Dataset.Properties, new Property("CatalogScan", "true"));
          top.addDataset(catref);
        }
      }
    }

    return catBuilder;
  }

}


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

package thredds.catalog;

/**
 * Type-safe enumeration of THREDDS coherent collection types.
 *
 * @author john caron
 */

public final class CollectionType {
    private static java.util.ArrayList<CollectionType> members = new java.util.ArrayList<CollectionType>(20);

    public final static CollectionType NONE = new CollectionType("");
    public final static CollectionType TIMESERIES = new CollectionType("TimeSeries");
    public final static CollectionType STATIONS = new CollectionType("Stations");
    public final static CollectionType FORECASTS = new CollectionType("ForecastModelRuns");

    private String name;
    private CollectionType(String s) {
      this.name = s;
      members.add(this);
    }

    private CollectionType(String name, boolean fake) {
      this.name = name;
    }

    /** @return all CollectionType objects */
    public static java.util.Collection<CollectionType> getAllTypes() { return members; }

    /**
     * Return the known CollectionType that matches the given name (ignoring case)
     * or null if the name is unknown.
     *
     * @param name name of the desired CollectionType.
     * @return CollectionType or null if no match.
     */
    public static CollectionType findType(String name) {
      if (name == null) return null;
      for (CollectionType m : members) {
        if (m.name.equalsIgnoreCase( name))
          return m;
      }
      return null;
    }

  /**
   * Return a CollectionType that matches the given name by either matching
   * a known type (ignoring case) or creating an unknown type.
   *
   * @param name name of the desired CollectionType
   * @return the named CollectionType or null if given name is null.
   */
    public static CollectionType getType(String name) {
      if (name == null) return null;
      CollectionType type = findType( name);
      return type != null ? type : new CollectionType( name, false);
    }

    /**
     * Return the collection name.
     */
     public String toString() {
        return name;
    }

     /** Override Object.hashCode() to be consistent with this equals. */
     public int hashCode() { return name.hashCode(); }
     /** CollectionType with same name are equal. */
     public boolean equals(Object o) {
       if (this == o) return true;
       if (!(o instanceof CollectionType)) return false;
       return o.hashCode() == this.hashCode();
    }
}