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

package dap4.core.dmr.parser;

import dap4.core.dmr.*;
import dap4.core.util.DapException;
import dap4.core.util.DapSort;
import dap4.core.util.DapUtil;
import dap4.core.util.Escape;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.util.*;

/**
 * Implement the Dap4 Parser Using a DOM Parser
 */

public class DOM4Parser implements Dap4Parser
{

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

    static final float DAPVERSION = 4.0f;
    static final float DMRVERSION = 1.0f;

    static final String DEFAULTATTRTYPE = "Int32";

    static final int RULENULL = 0;
    static final int RULEDIMREF = 1;
    static final int RULEMAPREF = 2;
    static final int RULEVAR = 3;
    static final int RULEMETADATA = 4;

    static final String[] RESERVEDTAGS = new String[]{
            "_edu.ucar"
    };

    static final BigInteger BIG_INT64_MAX = BigInteger.valueOf(Long.MAX_VALUE);

    static final DapSort[] METADATASCOPES = new DapSort[]{
            DapSort.DATASET,
            DapSort.GROUP,
            DapSort.DIMENSION,
            DapSort.MAP,
            DapSort.VARIABLE,
            DapSort.STRUCTURE,
            DapSort.SEQUENCE,
            DapSort.ATTRIBUTESET
    };

    static final Map<String, DapSort> sortmap;
    static final Map<String, TypeSort> typemap;

    static {
        sortmap = new HashMap<String, DapSort>();
        sortmap.put("attribute", DapSort.ATTRIBUTE);
        sortmap.put("dataset", DapSort.DATASET);
        sortmap.put("dim", DapSort.DIMENSION);
        sortmap.put("dimension", DapSort.DIMENSION);
        sortmap.put("enumeration", DapSort.ENUMERATION);
        sortmap.put("enumconst", DapSort.ENUMCONST);
        sortmap.put("group", DapSort.GROUP);
        sortmap.put("map", DapSort.MAP);
        sortmap.put("otherxml", DapSort.OTHERXML);
        sortmap.put("sequence", DapSort.SEQUENCE);
        sortmap.put("structure", DapSort.STRUCTURE);
        sortmap.put("char", DapSort.VARIABLE);
        sortmap.put("int8", DapSort.VARIABLE);
        sortmap.put("uint8", DapSort.VARIABLE);
        sortmap.put("int16", DapSort.VARIABLE);
        sortmap.put("uint16", DapSort.VARIABLE);
        sortmap.put("int32", DapSort.VARIABLE);
        sortmap.put("uint32", DapSort.VARIABLE);
        sortmap.put("int64", DapSort.VARIABLE);
        sortmap.put("uint64", DapSort.VARIABLE);
        sortmap.put("float32", DapSort.VARIABLE);
        sortmap.put("float64", DapSort.VARIABLE);
        sortmap.put("string", DapSort.VARIABLE);
        sortmap.put("url", DapSort.VARIABLE);
        sortmap.put("opaque", DapSort.VARIABLE);
        sortmap.put("enum", DapSort.VARIABLE);

        typemap = new HashMap<String, TypeSort>();
        //se lower cas enames
        typemap.put("char", TypeSort.Char);
        typemap.put("int8", TypeSort.Int8);
        typemap.put("uint8", TypeSort.UInt8);
        typemap.put("int16", TypeSort.Int16);
        typemap.put("uint16", TypeSort.UInt16);
        typemap.put("int32", TypeSort.Int32);
        typemap.put("uint32", TypeSort.UInt32);
        typemap.put("int64", TypeSort.Int64);
        typemap.put("uint64", TypeSort.UInt64);
        typemap.put("float32", TypeSort.Float32);
        typemap.put("float64", TypeSort.Float64);
        typemap.put("string", TypeSort.String);
        typemap.put("url", TypeSort.URL);
        typemap.put("opaque", TypeSort.Opaque);
        typemap.put("enum", TypeSort.Enum);
        typemap.put("structure", TypeSort.Structure);
        typemap.put("sequence", TypeSort.Sequence);

    }

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

    static protected int globaldebuglevel = 0;
    static protected java.io.PrintStream debugstream = System.err;

    //////////////////////////////////////////////////
    // Static methods

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

    public void setDebugStream(java.io.PrintStream stream)
    {
        if(stream != null)
            debugstream = stream;
    }

    static DapSort
    nodesort(Node n)
    {
        String elem = n.getNodeName();
        DapSort sort = sortmap.get(elem.toLowerCase());
        return sort;
    }

    static TypeSort
    nodetypesort(Node n)
    {
        String elem = n.getNodeName();
        TypeSort sort = typemap.get(elem.toLowerCase());
        return sort;
    }

    //////////////////////////////////////////////////
    // Instance variables

    protected DMRFactory factory = null;
    protected ErrorResponse errorresponse = null;
    protected Deque<DapNode> scopestack = new ArrayDeque<>();
    protected DapDataset root = null; // of the parse
    protected boolean trace = false;
    protected boolean debug = false;

    //////////////////////////////////////////////////
    // Parser State

    // Need to be able to map a DOM <Group> node to DapNode
    protected Map<Node, DapGroup> groupmap = new HashMap<>();

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

    public DOM4Parser(DMRFactory factory)
    {
        super();
        this.factory = (factory == null ? new DMRFactory() : factory);
        if(globaldebuglevel > 0) setDebugLevel(globaldebuglevel);
    }

    //////////////////////////////////////////////////
    // Accessors

    public void
    setDebugLevel(int level)
    {
        setGlobalDebugLevel(level);
    }

    public ErrorResponse getErrorResponse()
    {
        return errorresponse;
    }

    public DapDataset getDMR()
    {
        return this.root;
    }

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

    public boolean
    parse(String input)
            throws SAXException
    {
        try {
            DocumentBuilderFactory domfactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dombuilder = domfactory.newDocumentBuilder();
            StringReader rdr = new StringReader(input);
            InputSource src = new InputSource(rdr);
            Document doc = dombuilder.parse(src);
            doc.getDocumentElement().normalize();
            rdr.close();
            parseresponse(doc.getDocumentElement());
            return true;
        } catch (ParserConfigurationException | IOException e) {
            throw new SAXException(e);
        }
    }

    //////////////////////////////////////////////////
    // Parser specific methods

    DapGroup
    getGroupScope()
            throws ParseException
    {
        DapGroup gscope = (DapGroup) searchScope(DapSort.GROUP, DapSort.DATASET);
        if(gscope == null) throw new ParseException("Undefined Group Scope");
        return gscope;
    }

    DapNode
    getMetadataScope()
            throws ParseException
    {
        // Search up the stack for first match.
        DapNode match = searchScope(METADATASCOPES);
        if(match == null)
            throw new ParseException("No enclosing metadata capable scope");
        return match;
    }

    DapNode
    getParentScope()
            throws DapException
    {
        DapNode parent = searchScope(DapSort.STRUCTURE, DapSort.SEQUENCE, DapSort.GROUP, DapSort.DATASET, DapSort.ENUMERATION);
        if(parent == null) throw new DapException("Undefined parent Scope");
        return parent;
    }

    DapVariable
    getVariableScope()
            throws DapException
    {
        DapNode match = searchScope(DapSort.VARIABLE, DapSort.STRUCTURE, DapSort.SEQUENCE);
        if(match == null)
            throw new ParseException("No enclosing variable scope");
        return (DapVariable) match;
    }

    DapNode
    getScope(DapSort... sort)
            throws DapException
    {
        DapNode node = searchScope(sort);
        if(node == null) // return exception if not found
            throw new ParseException("No enclosing scope of specified type");
        return node;
    }

    DapNode
    searchScope(DapSort... sort)
    {
        Iterator it = scopestack.iterator();
        while(it.hasNext()) {
            DapNode node = (DapNode) it.next();
            for(int j = 0; j < sort.length; j++) {
                if(node.getSort() == sort[j])
                    return node;
            }
        }
        return null;
    }

    DapVariable
    findVariable(DapNode parent, String name)
    {
        DapVariable var = null;
        switch (parent.getSort()) {
        case DATASET:
        case GROUP:
            var = ((DapGroup) parent).findVariable(name);
            break;
        case STRUCTURE:
            var = (DapVariable) ((DapStructure) parent).findByName(name);
            break;
        case SEQUENCE:
            var = (DapVariable) ((DapSequence) parent).findByName(name);
            break;
        default:
            break;
        }
        return var;
    }

    // XML Attribute utilities
    protected String
    pull(Node n, String name)
    {
        NamedNodeMap map = n.getAttributes();
        Node attr = map.getNamedItem(name);
        if(attr == null)
            return null;
        return attr.getNodeValue();
    }

    //////////////////////////////////////////////////
    // Attribute construction

    DapAttribute
    makeAttribute(DapSort sort, String name, DapType basetype, List<String> nslist)
            throws ParseException
    {
        DapAttribute attr = factory.newAttribute(name, basetype);
        if(sort == DapSort.ATTRIBUTE) {
            attr.setBaseType(basetype);
        }
        attr.setNamespaceList(nslist);
        return attr;
    }

    boolean isempty(String text)
    {
        return (text == null || text.length() == 0);
    }

    boolean
    islegalenumtype(DapType kind)
    {
        return kind.isIntegerType();
    }

    boolean
    islegalattributetype(DapType kind)
    {
        return kind.isLegalAttrType();
    }

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

    protected void trace(String action)
    {
        if(!trace) return;
        debugstream.println("ACTION: " + action);
        debugstream.flush();
    }

    /**
     * Return the subnodes of a node with non-element nodes suppressed
     *
     * @param parent
     * @return list of element children
     */
    List<Node>
    getSubnodes(Node parent)
    {
        List<Node> subs = new ArrayList<>();
        NodeList nodes = parent.getChildNodes();
        for(int i = 0; i < nodes.getLength(); i++) {
            Node n = nodes.item(i);
            if(n.getNodeType() == Node.ELEMENT_NODE)
                subs.add(n);
        }
        return subs;
    }

    /**
     * Return all the text of a node else null
     *
     * @param n
     * @return text string | null
     */
    String
    getNodeText(Node n)
    {
        return n.getTextContent();
    }

    protected String
    cleanup(String value)
    {
        value = value.trim();
        int first = -1;
        for(int i = 0; i < value.length(); i++) {
            if(first < 0 && value.charAt(i) > ' ') {
                first = i;
                break;
            }
        }
        int last = -1;
        for(int i = value.length() - 1; i >= 0; i--) {
            if(last < 0 && value.charAt(i) > ' ') {
                last = i;
                break;
            }
        }
        if(last < 0) last = value.length() - 1;
        if(first < 0) first = 0;
        value = value.substring(first, last + 1);
        return value;
    }

    protected void
    addField(DapVariable instance, DapVariable field)
            throws DapException
    {
        DapType t = instance.getBaseType();
        addField(t, field);
    }

    protected void
    addField(DapType t, DapVariable field)
            throws DapException
    {
        switch (t.getTypeSort()) {
        case Structure:
        case Sequence:
            ((DapStructure) t).addField(field);
            field.setParent(t);
            break;
        default:
            assert false : "Container must be struct or seq";
        }
    }

    protected void
    recorddecl(DapNode n, DapGroup parent)
            throws ParseException
    {
        try {
            parent.addDecl(n);
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected void
    recordfield(DapVariable var, DapStructure parent)
            throws ParseException
    {
        try {
            addField(parent, var);
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected void
    recordattr(DapAttribute attr, DapNode parent)
            throws ParseException
    {
        try {
            parent.addAttribute(attr);
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    //////////////////////////////////////////////////
    // Recursive descent parser

    protected void
    parseresponse(Node root)
            throws ParseException
    {
        String elemname = root.getNodeName();
        if(elemname.equalsIgnoreCase("Error")) {
            parseerror(root);
        } else if(elemname.equalsIgnoreCase("Dataset")) {
            parsedataset(root);
        } else
            throw new ParseException("Unexpected response root: " + elemname);
    }

    protected void
    parsedataset(Node rootnode)
            throws ParseException
    {
        if(trace) trace("dataset.enter");
        String name = pull(rootnode, "name");
        String dapversion = pull(rootnode, "dapVersion");
        if(dapversion == null) dapversion = pull(rootnode, "dapversion");
        String dmrversion = pull(rootnode, "dmrVersion");
        if(dmrversion == null) dmrversion = pull(rootnode, "dmrversion");
        if(isempty(name))
            throw new ParseException("Empty dataset name attribute");
        // convert and test version numbers
        float ndapversion = DAPVERSION;
        if(dapversion != null)
            try {
                ndapversion = Float.parseFloat(dapversion);
            } catch (NumberFormatException nfe) {
                ndapversion = DAPVERSION;
            }
        if(ndapversion != DAPVERSION)
            throw new ParseException("Dataset dapVersion mismatch: " + dapversion);
        float ndmrversion = DAPVERSION;
        if(dmrversion != null)
            try {
                ndmrversion = Float.parseFloat(dmrversion);
            } catch (NumberFormatException nfe) {
                ndmrversion = DMRVERSION;
            }
        if(ndmrversion != DMRVERSION)
            throw new ParseException("Dataset dmrVersion mismatch: " + dmrversion);
        this.root = factory.newDataset(name);
        this.root.setDapVersion(Float.toString(ndapversion));
        this.root.setDMRVersion(Float.toString(ndmrversion));
        this.root.setDataset(this.root);
        passReserved(rootnode, this.root);
        scopestack.push(this.root);
        // We need to first process (recursively) all
        // definitional nodes: subgroups, enumdefs, and dimensions.
        fillgroupdefs(rootnode, this.root);
        // Second, we need to recursively define all variables
        fillgroupvars(rootnode, this.root);
        if(trace) trace("dataset.exit");
        assert (scopestack.peek() != null && scopestack.peek().getSort() == DapSort.DATASET);
        // finalize
        this.root.sort();
        scopestack.pop();
        if(!scopestack.isEmpty())
            throw new ParseException("Dataset: nested dataset");
        this.root.finish();
    }

    protected void
    fillgroupdefs(Node domgroup, DapGroup group)
            throws ParseException
    {
        try {
            if(trace) trace("fillgroupdefs.enter");
            List<Node> nodes = getSubnodes(domgroup);
            for(int i = 0; i < nodes.size(); i++) {
                Node n = nodes.get(i);
                String name = pull(n, "name");
                if(isempty(name))
                    throw new ParseException("Fillgroup: Empty node name");
                DapSort sort = nodesort(n);
                switch (sort) {
                case ENUMERATION:
                    recorddecl(parseenumdef(n), group);
                    break;
                case DIMENSION:
                    recorddecl(parsedimdef(n), group);
                    break;
                case GROUP:
                    DapGroup g = parsegroupdefs(n);
                    recorddecl(g, group);
                    groupmap.put(n, g);
                    break;
                default:
                    break; // ignore
                }
            }
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected void
    fillgroupvars(Node domgroup, DapGroup group)
            throws ParseException
    {
        try {
            if(trace) trace("fillgroupvars.enter");
            List<Node> nodes = getSubnodes(domgroup);
            for(int i = 0; i < nodes.size(); i++) {
                Node n = nodes.get(i);
                String name = pull(n, "name");
                if(isempty(name))
                    throw new ParseException("Fillgroup: Empty node name");
                DapSort sort = nodesort(n);
                switch (sort) {
                case GROUP:
                    // Get the DapGroup (should have been defined
                    // by parsegroupdefs)
                    DapGroup ngroup = groupmap.get(n);
                    assert (ngroup != null);
                    parsegroupvars(n, ngroup);
                    break;
                case VARIABLE:
                    TypeSort type = nodetypesort(n);
                    if(type.isEnumType())
                        recorddecl(parseenumvar(n), group);
                    else if(type.isAtomic())
                        recorddecl(parseatomicvar(n), group);
                    else
                        throw new ParseException("Illegal variable type: " + name);
                    break;
                case STRUCTURE:
                    DapVariable v = parsestructvar(n);
                    // Track variable and its type
                    recorddecl(v.getBaseType(), group);
                    recorddecl(v, group);
                    break;
                case SEQUENCE:
                    v = parseseqvar(n);
                    // Track variable and its type
                    recorddecl(v.getBaseType(), group);
                    recorddecl(v, group);
                    break;
                case ATTRIBUTESET:
                    recordattr(parseattrset(n), group);
                    break;
                case ATTRIBUTE:
                    recordattr(parseattr(n), group);
                    break;
                case OTHERXML:
                    recordattr(parseotherxml(n), group);
                    break;
                case ENUMERATION:
                case DIMENSION:
                    break; // ignore
                default:
                    throw new ParseException("Unexpected element: " + n);
                }
            }
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected DapGroup
    parsegroupdefs(Node node)
            throws DapException
    {
        if(trace) trace("groupdefs.enter");
        String name = pull(node, "name");
        if(isempty(name))
            throw new ParseException("Empty group name");
        try {
            DapGroup g = factory.newGroup(name);
            passReserved(node, g);
            scopestack.push(g);
            fillgroupdefs(node, g);
            if(trace) trace("group.exit");
            scopestack.pop();
            return g;
        } catch (DapException de) {
            throw new ParseException(de);
        }
    }

    protected DapEnumeration
    parseenumdef(Node node)
            throws ParseException
    {
        try {
            if(trace) trace("enumdef.enter");
            String name = pull(node, "name");
            if(isempty(name))
                throw new ParseException("Enumdef: Empty Enum Declaration name");
            String typename = pull(node, "basetype");
            DapType basedaptype = null;
            if(typename == null) {
                basedaptype = DapEnumeration.DEFAULTBASETYPE;
            } else {
                if("Byte".equalsIgnoreCase(typename)) typename = "UInt8";
                basedaptype = (DapType) this.root.lookup(typename, DapSort.ATOMICTYPE);
                if(basedaptype == null || !islegalenumtype(basedaptype))
                    throw new ParseException("Enumdef: Invalid Enum Declaration Type name: " + typename);
            }
            DapEnumeration dapenum = factory.newEnumeration(name, basedaptype);
            scopestack.push(dapenum);
            List<DapEnumConst> econsts = parseenumconsts(node);
            if(econsts.size() == 0)
                throw new ParseException("Enumdef: no enum constants specified");
            DapEnumeration eparent = (DapEnumeration) scopestack.pop();
            eparent.setEnumConsts(econsts);
            if(trace) trace("enumdef.exit");
            return dapenum;
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected List<DapEnumConst>
    parseenumconsts(Node enumdef)
            throws ParseException
    {
        if(trace) trace("enumconsts.enter");
        List<DapEnumConst> econsts = new ArrayList<>();
        List<Node> nodes = getSubnodes(enumdef);
        for(int i = 0; i < nodes.size(); i++) {
            Node n = nodes.get(i);
            DapEnumConst dec = parseenumconst(n);
            econsts.add(dec);
        }
        if(trace) trace("enumconsts.exit");
        return econsts;
    }

    protected DapEnumConst
    parseenumconst(Node node)
            throws ParseException
    {
        try {
            if(trace) trace("enumconst.enter");
            String name = pull(node, "name");
            String value = pull(node, "value");
            if(isempty(name))
                throw new ParseException("Enumconst: Empty enum constant name");
            if(isempty(value))
                throw new ParseException("Enumdef: Invalid enum constant value: " + value);
            long lvalue = 0;
            try {
                BigInteger bivalue = new BigInteger(value);
                bivalue = DapUtil.BIG_UMASK64.and(bivalue);
                lvalue = bivalue.longValue();
            } catch (NumberFormatException nfe) {
                throw new ParseException("Enumconst: illegal value: " + value);
            }
            DapEnumeration parent = (DapEnumeration) getScope(DapSort.ENUMERATION);
            // Verify that the name is a legal enum constant name, which is restricted
            // vis-a-vis other names
            if(!ParseUtil.isLegalEnumConstName(name))
                throw new ParseException("Enumconst: illegal enumeration constant name: " + name);
            DapEnumConst dec = new DapEnumConst(name, lvalue);
            return dec;
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected DapGroup
    parsegroupvars(Node node, DapGroup group)
            throws DapException
    {
        if(trace) trace("groupvars.enter");
        String name = pull(node, "name");
        try {
            scopestack.push(group);
            fillgroupvars(node, group);
            if(trace) trace("groupvars.exit");
            scopestack.pop();
            return group;
        } catch (DapException de) {
            throw new ParseException(de);
        }
    }

    protected DapDimension
    parsedimdef(Node node)
            throws ParseException
    {
        if(trace) trace("dimdef.enter");
        String name = pull(node, "name");
        String size = pull(node, "size");
        long lvalue = 0;
        if(isempty(name))
            throw new ParseException("Dimdef: Empty dimension declaration name");
        if(isempty(size))
            throw new ParseException("Dimdef: Empty dimension declaration size");
        try {
            lvalue = Long.parseLong(size);
            if(lvalue <= 0)
                throw new ParseException("Dimdef: value <= 0: " + lvalue);
        } catch (NumberFormatException nfe) {
            throw new ParseException("Dimdef: non-integer value: " + size);
        }
        DapDimension dim = factory.newDimension(name, lvalue);
        dim.setShared(true);
        if(trace) trace("dimdef.exit");
        return dim;
    }

    protected DapVariable
    parseenumvar(Node node)
            throws ParseException
    {
        if(trace) trace("enumvar.enter");
        String typename = node.getNodeName();
        assert ("enum".equalsIgnoreCase(typename));
        try {
            String enumfqn = pull(node, "enum");
            if(isempty(enumfqn))
                throw new ParseException("Enumvariable: Empty enum type name");
            DapType basetype = (DapEnumeration) root.findByFQN(enumfqn, DapSort.ENUMERATION);
            if(basetype == null)
                throw new ParseException("EnumVariable: no such enum: " + enumfqn);
            String name = pull(node, "name");
            if(isempty(name))
                throw new ParseException("Enumvar: Empty variable name");
            DapVariable var = factory.newVariable(name, basetype);
            passReserved(node, var);
            scopestack.push(var);
            fillmetadata(node, var);
            scopestack.pop();
            return var;
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected DapVariable
    parseatomicvar(Node node)
            throws ParseException
    {
        if(trace) trace("atomicvariable.enter");
        String name = pull(node, "name");
        if(isempty(name))
            throw new ParseException("Atomicvariable: Empty variable name");
        String typename = node.getNodeName();
        if("Byte".equals(typename)) typename = "UInt8"; // special case
        try {
            DapType basetype = (DapType) this.root.lookup(typename, DapSort.ATOMICTYPE);
            if(basetype == null)
                throw new ParseException("AtomicVariable: Illegal type: " + typename);
            DapVariable var = factory.newVariable(name, basetype);
            passReserved(node, var);
            scopestack.push(var);
            fillmetadata(node, var);
            scopestack.pop();
            return var;
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected DapVariable
    parsestructvar(Node node)
            throws ParseException
    {
        if(trace) trace("structvariable.enter");
        String name = pull(node, "name");
        if(isempty(name))
            throw new ParseException("Structvar: Empty variable name");
        String typename = node.getNodeName();
        try {
            DapStructure type = factory.newStructure(name);
            DapVariable var = factory.newVariable(name, type);
            passReserved(node, var);
            scopestack.push(type);
            fillcontainer(node, type);
            scopestack.pop();
            scopestack.push(var);
            fillmetadata(node, var);
            scopestack.pop();
            return var;
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected DapVariable
    parseseqvar(Node node)
            throws ParseException
    {
        if(trace) trace("seqvariable.enter");
        String name = pull(node, "name");
        if(isempty(name))
            throw new ParseException("Seqvar: Empty variable name");
        String typename = node.getNodeName();
        try {
            DapSequence type = factory.newSequence(name);
            DapVariable var = factory.newVariable(name, type);
            passReserved(node, var);
            scopestack.push(type);
            fillcontainer(node, type);
            scopestack.pop();
            scopestack.push(var);
            fillmetadata(node, var);
            scopestack.pop();
            return var;
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected void
    fillcontainer(Node node, DapStructure parent)
            throws ParseException
    {
        try {
            if(trace) trace("fillcontainer.enter");
            List<Node> nodes = getSubnodes(node);
            // Fields first
            for(int i = 0; i < nodes.size(); i++) {
                Node n = nodes.get(i);
                DapSort sort = nodesort(n);
                switch (sort) {
                case VARIABLE:
                    recordfield(parseatomicvar(n), parent);
                    break;
                case STRUCTURE:
                    DapVariable dv = parsestructvar(n);
                    // Track variable and its type
                    recorddecl(dv.getBaseType(), getGroupScope());
                    // In order to get fqns correct, we need to re-parent
                    // the type as parent
                    dv.getBaseType().overrideParent(parent);
                    recordfield(dv, parent);
                    break;
                case SEQUENCE:
                    dv = parseseqvar(n);
                    // Track variable and its type
                    recorddecl(dv.getBaseType(), getGroupScope());
                    recordfield(dv, parent);
                    break;
                default:
                    break; // ignore for now
                }
            }
            if(trace) trace("fillcontainer.exit");
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected void
    fillmetadata(Node node, DapVariable var)
            throws ParseException
    {
        try {
            if(trace) trace("fillmetadata.enter");
            List<Node> nodes = getSubnodes(node);
            for(int i = 0; i < nodes.size(); i++) {
                Node n = nodes.get(i);
                DapSort sort = nodesort(n);
                switch (sort) {
                case DIMENSION:
                    var.addDimension(parsedimref(n));
                    break;
                case MAP:
                    var.addMap(parsemap(n));
                    break;
                case ATTRIBUTE:
                    recordattr(parseattr(n), var);
                    break;
                case ATTRIBUTESET:
                    recordattr(parseattrset(n), var);
                    break;
                case OTHERXML:
                    recordattr(parseotherxml(n), var);
                    break;
                default:
                    break; // ignore for now
                }
            }
            if(trace) trace("fillmetadata.exit");
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected DapDimension
    parsedimref(Node node)
            throws ParseException
    {
        try {
            if(trace) trace("dimref.enter");
            String dimname = pull(node, "name");
            String size = pull(node, "size");
            DapDimension dim;
            if(dimname != null && size != null)
                throw new ParseException("Dimref: both name and size specified");
            if(dimname == null && size == null)
                throw new ParseException("Dimref: no name or size specified");
            if(dimname != null && isempty(dimname))
                throw new ParseException("Dimref: Empty dimension reference name");
            else if(size != null && isempty(size))
                throw new ParseException("Dimref: Empty dimension size");
            if(dimname != null) {
                dim = (DapDimension) this.root.findByFQN(dimname, DapSort.DIMENSION);
            } else {// size != null; presume a number; create unique anonymous dimension
                size = size.trim();
                long anonsize;
                try {
                    anonsize = Long.parseLong(size.trim());
                } catch (NumberFormatException nfe) {
                    throw new ParseException("Dimref: Illegal dimension size");
                }
                // Create in root group
                dim = this.root.createAnonymous(anonsize);
            }
            if(dim == null)
                throw new ParseException("Unknown dimension: " + dimname);
            return dim;
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected DapMap
    parsemap(Node node)
            throws ParseException
    {
        if(trace) trace("map.enter");
        String name = pull(node, "name");
        if(isempty(name))
            throw new ParseException("Mapref: Empty map name");
        DapVariable target;
        try {
            target = (DapVariable) this.root.findByFQN(name, DapSort.VARIABLE, DapSort.SEQUENCE, DapSort.STRUCTURE);
        } catch (DapException de) {
            throw new ParseException(de);
        }
        if(target == null)
            throw new ParseException("Mapref: undefined target variable: " + name);
        // Verify that this is a legal map =>
        // 1. it is outside the scope of its parent if the parent
        //    is a structure.
        DapNode container = target.getContainer();
        DapNode scope;
        try {
            scope = getParentScope();
        } catch (DapException de) {
            throw new ParseException(de);
        }
        if((container.getSort() == DapSort.STRUCTURE || container.getSort() == DapSort.SEQUENCE)
                && container == scope)
            throw new ParseException("Mapref: map target variable not in outer scope: " + name);
        DapMap map = factory.newMap(target);
        if(trace) trace("map.exit");
        return map;
    }

    protected DapAttribute
    parseattr(Node node)
            throws ParseException
    {
       try {
           if(trace) trace("attribute.enter");
           String name = pull(node, "name");
           if(isempty(name))
               throw new ParseException("Attribute: Empty attribute name");
           String type = pull(node, "type");
           if(isempty(type))
               type = DEFAULTATTRTYPE;
           else if("Byte".equalsIgnoreCase(type)) type = "UInt8";
           // Convert type to basetype
           DapType basetype = (DapType) this.root.lookup(type,
                   DapSort.ENUMERATION, DapSort.ATOMICTYPE);
           if(basetype == null)
               throw new ParseException("parseattr: Illegal type: " + type);
           List<String> nslist = parsenamespaces(node);
           DapAttribute attr = makeAttribute(DapSort.ATTRIBUTE, name, basetype, nslist);
           scopestack.push(attr);
           List<String> values = new ArrayList<String>();
           // See first if we have a "value" xml attribute
           String val = pull(node, "value");
           if(val != null) {
               values.add(val);
           } else {  // Look for <value> subnodes
               if(node.hasChildNodes()) {
                   List<Node> nodes = getSubnodes(node);
                   for(int i = 0; i < nodes.size(); i++) {
                       Node n = nodes.get(i);
                       String kind = n.getNodeName();
                       if(kind.equalsIgnoreCase("Value")) {
                           // two case: <value value="..."/>
                           // or <value>...</value>
                           val = pull(n, "value");
                           if(val != null)
                               values.add(val);
                           else
                               values.add(getNodeText(n));
                       } else
                           throw new ParseException("Unexpected non-value element in attribute");
                   }
               } else {
                   values.add(cleanup(node.getTextContent()));
               }
           }
           if(values.size() == 0)
               throw new ParseException("Attribute: attribute has no values");
           // Need to normalize the values
           for(int i = 0; i < values.size(); i++) {
               String s = values.get(i);
               if(s == null) s = "";
               String ds = Escape.backslashUnescape(s);
               values.set(i, ds);
           }
           attr.setValues(values.toArray(new String[values.size()]));
           scopestack.pop();
           if(trace) trace("attribute.exit");
           return attr;
       } catch (DapException e) {
           throw new ParseException(e);
       }
    }

    protected DapAttributeSet
    parseattrset(Node node)
            throws ParseException
    {
        try {
            if(trace) trace("attrset.enter");
            String name = pull(node, "name");
            if(isempty(name))
                throw new ParseException("AttributeSet: Empty attribute name");
            List<String> nslist = parsenamespaces(node);
            DapAttributeSet attrset = (DapAttributeSet) makeAttribute(DapSort.ATTRIBUTESET, name, null, nslist);
            scopestack.push(attrset);
            List<Node> nodes = getSubnodes(node);
            for(int i = 0; i < nodes.size(); i++) {
                Node n = nodes.get(i);
                DapSort sort = nodesort(n);
                switch (sort) {
                case ATTRIBUTE:
                    recordattr(parseattr(n), attrset);
                    break;
                case ATTRIBUTESET:
                    recordattr(parseattrset(n), attrset);
                    break;
                default:
                    throw new ParseException("Unexpected attribute set element: " + n);
                }
            }
            scopestack.pop();
            if(trace) trace("attributeset.exit");
            return attrset;
        } catch (DapException e) {
            throw new ParseException(e);
        }
    }

    protected List<String>
    parsenamespaces(Node node)
            throws ParseException
    {
        List<String> nslist = new ArrayList<>();
        List<Node> nodes = getSubnodes(node);
        for(int i = 0; i < nodes.size(); i++) {
            Node n = nodes.get(i);
            if("namespace".equalsIgnoreCase(n.getNodeName())) {
                String ns = pull(n, "href");
                if(isempty(ns))
                    throw new ParseException("Illegal null namespace href: " + node);
                if(!nslist.contains(ns)) nslist.add(ns);
            }
        }
        return nslist;
    }

    protected DapAttribute
    parseotherxml(Node node)
            throws ParseException
    {
        if(trace) trace("otherxml.enter");
        String name = pull(node, "name");
        DapOtherXML other = factory.newOtherXML(name);
        // Get the child node(s)
        List<Node> nodes = getSubnodes(node);
        switch (nodes.size()) {
        case 0:
            break;
        case 1:
            other.setRoot(nodes.get(0));
            break;
        default:
            throw new ParseException("OtherXML: multiple top level nodes not supported");
        }
        if(trace) trace("otherxml.exit");
        return other;
    }

    protected void
    parseerror(Node node)
            throws ParseException
    {
        if(trace) trace("error.enter");
        String xhttpcode = pull(node, "httpcode");
        String shttpcode = (xhttpcode == null ? "400" : xhttpcode);
        int httpcode = 0;
        try {
            httpcode = Integer.parseInt(shttpcode);
        } catch (NumberFormatException nfe) {
            throw new ParseException("Error Response; illegal http code: " + shttpcode);
        }
        this.errorresponse = new ErrorResponse();
        this.errorresponse.setCode(httpcode);
        if(trace) trace("error.exit");
    }

    protected void
    errormessage(String value)
            throws ParseException
    {
        if(trace) trace("errormessage.enter");
        assert (this.errorresponse != null) : "Internal Error";
        String message = value;
        message = Escape.entityUnescape(message); // Remove XML encodings
        this.errorresponse.setMessage(message);
        if(trace) trace("errormessage.exit");
    }

    protected void
    errorcontext(String value)
            throws ParseException
    {
        if(trace) trace("errorcontext.enter");
        assert (this.errorresponse != null) : "Internal Error";
        String context = value;
        context = Escape.entityUnescape(context); // Remove XML encodings
        this.errorresponse.setContext(context);
        if(trace) trace("errorcontext.exit");
    }

    protected void
    errorotherinfo(String value)
            throws ParseException
    {
        if(trace) trace("errorotherinfo.enter");
        assert (this.errorresponse != null) : "Internal Error";
        String other = value;
        other = Escape.entityUnescape(other); // Remove XML encodings
        this.errorresponse.setOtherInfo(other);
        if(trace) trace("errorotherinfo.exit");
    }

    /**
     * Pass reserved xml attributes unchanged
     *
     * @param node
     * @param dap
     * @throws ParseException
     */
    protected void
    passReserved(Node node, DapNode dap)
            throws ParseException
    {
        try {
            NamedNodeMap attrs = node.getAttributes();
            for(int i = 0; i < attrs.getLength(); i++) {
                Node n = attrs.item(i);
                String key = n.getNodeName();
                String value = n.getNodeValue();
                if(isReserved(key))
                    dap.addXMLAttribute(key, value);
            }
        } catch (DapException de) {
            throw new ParseException(de);
        }
    }

    static boolean
    isReserved(String name)
    {
        for(String tag : RESERVEDTAGS) {
            if(name.startsWith(tag)) return true;
        }
        return false;
    }
}


package ucar.nc2.ft2.simpgeometry;

import java.util.List;

import ucar.nc2.Dimension;
import ucar.nc2.Variable;
import ucar.nc2.constants.CF;

/**
 * Helpers for various simple geometry tasks
 * relating to the CF standard.
 *
 * @author wchen@usgs.gov
 *
 */
public class CFSimpleGeometryHelper {

	/**
	 * Gets the subset string  to be used in NetCDFFile.read given a variable and some indicies.
	 * useful for subsetting timeseries
	 *
	 *
	 * @param var variable to subset
	 * @param beginInd beginning index (inclusive)
	 * @param endInd end index (exclusive)
	 * @param id The SimpleGeometryID to index
	 * @return subset string
	 */
	public static String getSubsetString(Variable var, int beginInd, int endInd, int id){
		if(var == null) return null;

		String subStr = "";

		List<Dimension> dimList = var.getDimensions();

		// Enforce two dimension arrays
		if(dimList.size() > 2 || dimList.size() < 1) {
			return null;
		}

		for(int i = 0; i < dimList.size(); i++) {

			Dimension dim = dimList.get(i);
			if(dim == null) continue;

			// If not CF Time then select only that ID
			if(!CF.TIME.equalsIgnoreCase(dim.getShortName()) && !CF.TIME.equalsIgnoreCase(dim.getFullNameEscaped())) {
				subStr += id;
			}

			// Otherwise subset based on time
			else {

				if(beginInd < 0 || endInd < 0) subStr += ":";
				else subStr += (beginInd + ":" + endInd);
			}

			if(i < dimList.size() - 1) {
				subStr += ",";
			}
		}

		return subStr;
	}

	/**
	 * Gets the subset string  to be used in NetCDFFile.read given a variable and an ID.
	 * useful for subsetting timeseries. This version will take the whole timeseries.
	 *
	 *
	 * If a time dimension is not present, however, it will default to id first and then subsetter second.
	 *
	 * @param var variable to subset
	 * @param id The SimpleGeometryID to index
	 * @return subset string
	 */
	public static String getSubsetString(Variable var, int id) {
		return getSubsetString(var, -1, -1, id);
	}
}


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

import ucar.nc2.Structure;
import ucar.nc2.Variable;
import ucar.nc2.Sequence;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Concrete implementation of ArrayStructure, data storage is in member arrays, which are converted to
 *   StructureData member data on the fly.
 * This defers object creation for efficiency. Use getJavaArrayXXX<type>() and getScalarXXX<type>() data accessors if possible.
 *
 * How to create:
 *

    ArrayStructureMA asma = new ArrayStructureMA( smembers, getShape());
    for (int i = 0; i < orgVariables.size(); i++) {
      Variable v = (Variable) orgVariables.get(i);
      Array data = v.read();
      asma.setMemberArray( v.getName(), data);
    }

 * How to do Nested Structures:

   Structure {
     float f1;
     short f2(3);

     Structure {
       int g1;
       double(2) g2;
       double(3,4) g3;

       Structure {
         int h1;
         double(2) h2;
       } nested2(7);

     } nested1(12);
   } s(4);

For f1, you need an ArrayFloat of shape {4}

For f2, you need an ArrayShort of shape {4, 3} .

For nested1, you need an ArrayStructure of shape {4, 12}.
   Use an ArrayStructureMA that has 3 members:

For g1, you need an ArrayInt of shape (4, 12}

For g2, you need an ArrayDouble of shape {4, 12, 2}.

For g3, you need an ArrayDouble of shape {4, 12, 3, 4}.

For nested2, you need an ArrayStructure of shape {4, 12, 7}.
   Use an ArrayStructureMA that has 2 members:

For h1, you need an ArrayInt of shape (4, 12, 7}

For h2, you need an ArrayDouble of shape {4, 12, 7, 2}.

   Example code:

  public void testMA() throws IOException, InvalidRangeException {
    StructureMembers members = new StructureMembers("s");

    StructureMembers.Member m = members.addMember("f1", "desc", CDM.UNITS, DataType.FLOAT, new int[]{1});
    Array data = Array.factory(DataType.FLOAT, new int[]{4});
    m.setDataArray(data);
    fill(data);

    m = members.addMember("f2", "desc", CDM.UNITS, DataType.SHORT, new int[]{3});
    data = Array.factory(DataType.SHORT, new int[]{4, 3});
    m.setDataArray(data);
    fill(data);

    m = members.addMember("nested1", "desc", CDM.UNITS, DataType.STRUCTURE, new int[]{9});
    data = makeNested1(m);
    m.setDataArray(data);

    ArrayStructureMA as = new ArrayStructureMA(members, new int[]{4});
    //System.out.println( NCdumpW.printArray(as, "", null));
    new TestStructureArray().testArrayStructure(as);

    // get f2 out of the 3nd "s"
    StructureMembers.Member f2 = as.getStructureMembers().findMember("f2");
    short[] f2data = as.getJavaArrayShort(2, f2);
    assert f2data[0] == 20;
    assert f2data[1] == 21;
    assert f2data[2] == 22;

    // get nested1 out of the 3nd "s"
    StructureMembers.Member nested1 = as.getStructureMembers().findMember("nested1");
    ArrayStructure nested1Data = as.getArrayStructure(2, nested1);

    // get g1 out of the 7th "nested1"
    StructureMembers.Member g1 = nested1Data.getStructureMembers().findMember("g1");
    int g1data = nested1Data.getScalarInt(6, g1);
    assert g1data == 26;

    // get nested2 out of the 7th "nested1"
    StructureMembers.Member nested2 = nested1Data.getStructureMembers().findMember("nested2");
    ArrayStructure nested2Data = nested1Data.getArrayStructure(6, nested2);

    // get h1 out of the 4th "nested2"
    StructureMembers.Member h1 = nested2Data.getStructureMembers().findMember("h1");
    int val = nested2Data.getScalarInt(4, h1);
    assert (val == 264);
  }

  public ArrayStructure makeNested1(StructureMembers.Member parent) throws IOException, InvalidRangeException {
    StructureMembers members = new StructureMembers(parent.getName());
    parent.setStructureMembers(members);

    StructureMembers.Member m = members.addMember("g1", "desc", CDM.UNITS, DataType.INT, new int[]{1});
    Array data = Array.factory(DataType.INT, new int[]{4, 9});
    m.setDataArray(data);
    fill(data);

    m = members.addMember("g2", "desc", CDM.UNITS, DataType.DOUBLE, new int[]{2});
    data = Array.factory(DataType.DOUBLE, new int[]{4, 9, 2});
    m.setDataArray(data);
    fill(data);

    m = members.addMember("g3", "desc", CDM.UNITS, DataType.DOUBLE, new int[]{3, 4});
    data = Array.factory(DataType.DOUBLE, new int[]{4, 9, 3, 4});
    m.setDataArray(data);
    fill(data);

    m = members.addMember("nested2", "desc", CDM.UNITS, DataType.STRUCTURE, new int[]{7});
    data = makeNested2(m);
    m.setDataArray(data);

    return new ArrayStructureMA(members, new int[]{4, 9});
  }

  public ArrayStructure makeNested2(StructureMembers.Member parent) throws IOException, InvalidRangeException {
    StructureMembers members = new StructureMembers(parent.getName());
    parent.setStructureMembers(members);

    StructureMembers.Member m = members.addMember("h1", "desc", CDM.UNITS, DataType.INT, new int[]{1});
    Array data = Array.factory(DataType.INT, new int[]{4, 9, 7});
    m.setDataArray(data);
    fill(data);

    m = members.addMember("h2", "desc", CDM.UNITS, DataType.DOUBLE, new int[]{2});
    data = Array.factory(DataType.DOUBLE, new int[]{4, 9, 7, 2});
    m.setDataArray(data);
    fill(data);

    return new ArrayStructureMA(members, new int[]{4, 9, 7});
  }

 * @author caron
 * @see Array
 */
public class ArrayStructureMA extends ArrayStructure {
  /* Implementation notes
     Most of the methods are now the default methods in the superclass, so that other subclasses can call them.
     This happens when the data is "enhanced", member arrays are set and must override the other possible storage methods.
   */

  /**
   * Create a new Array of type StructureData and the given members and shape.
   *
 You must set the data Arrays on each of the Members, using setDataObject(). These data Arrays contain the data
   * for that member Variable, for all the StructureData. Therefore it has rank one greater that the Members. The extra
   * dimension must be the outermost (slowest varying) dimension. ie, if some member has shape [3,10], the array would have
   * shape [nrows, 3, 10].
   *
   * @param members a description of the structure members
   * @param shape       the shape of the Array.
   */
  public ArrayStructureMA(StructureMembers members, int[] shape) {
    super(members, shape);
  }

  public ArrayStructureMA(StructureMembers members, int[] shape, StructureData[] sdata) {
    super(members, shape);
    if (nelems != sdata.length)
      throw new IllegalArgumentException("StructureData length= "+sdata.length+"!= shape.length="+nelems);
    this.sdata = sdata;
  }

  /**
   * Turn any ArrayStructure into a ArrayStructureMA
   * @param from copy from here. If from is a ArrayStructureMA, return it.
   * @return equivalent ArrayStructureMA
   * @throws java.io.IOException on error reading a sequence
   */
  static public ArrayStructureMA factoryMA(ArrayStructure from) throws IOException {
    if (from instanceof ArrayStructureMA)
      return (ArrayStructureMA) from;

    // To create an ArrayStructureMA that we can iterate over later, we need to know the shape of "from".
    if (from.getSize() > 0) {
      ArrayStructureMA to = new ArrayStructureMA(new StructureMembers(from.getStructureMembers()), from.getShape());
      for (StructureMembers.Member m : from.getMembers()) {
        to.setMemberArray(m.getName(), from.extractMemberArray(m));
      }
      return to;
    }

    // from.getSize() <= 0. This usually means that "from" is an ArraySequence, and that we won't know its size until
    // we iterate over it. extractMemberArray() will do that iteration for us, and then we can use the size of the
    // array it returns to determine the shape of "from".

    int numRecords = -1;
    Map<String, Array> memberArrayMap = new LinkedHashMap<>();

    for (StructureMembers.Member m : from.getMembers()) {
      Array array = from.extractMemberArray(m);
      assert array.getSize() > 0 : "array's size should have been computed in extractMemberArray().";
      int firstDimLen = array.getShape()[0];

      if (numRecords == -1) {
        numRecords = firstDimLen;
      } else {
        assert numRecords == firstDimLen : String.format("Expected all structure members to have the same first" +
                "dimension length, but %d != %d.", numRecords, firstDimLen);
      }

      memberArrayMap.put(m.getName(), array);
    }

    int[] shape;
    if (numRecords == -1) {
      shape = new int[] { 0 };  // "from" really was empty.
    } else {
      shape = new int[] { numRecords };
    }

    ArrayStructureMA to = new ArrayStructureMA(new StructureMembers(from.getStructureMembers()), shape);

    for (Map.Entry<String, Array> entry : memberArrayMap.entrySet()) {
      to.setMemberArray(entry.getKey(), entry.getValue());
    }

    return to;
  }

  @Override
  protected StructureData makeStructureData( ArrayStructure as, int index) {
    return new StructureDataA( as, index);
  }

  /**
   * Set the data array for this member.
   * @param memberName name of member
   * @param data Array for this member.
   */
  public void setMemberArray( String memberName, Array data) {
    StructureMembers.Member m = members.findMember( memberName);
    m.setDataArray( data);
  }

  public Array getArray(int recno, StructureMembers.Member m) {
    if (m.isVariableLength()) {
      Array data = m.getDataArray();
      if (data instanceof ArrayObject) {
        ArrayObject ao = (ArrayObject) data;
        Object dao = ao.getObject(recno);
        assert dao instanceof Array;
        return (Array) dao;
      }
    }
    return super.getArray(recno, m);
  }

  /**
   * Create an ArrayStructure for a Structure. Allow nested Structures.
   * Create the data arrays, and an iterator.
   *
   * @param from copy from here. If from is a ArrayStructureMA, return it.
   * @param shape the shape of the ArrayStructure
   * @return equivilent ArrayStructureMA
   * @throws java.io.IOException on error reading a sequence
   */
  static public ArrayStructureMA factoryMA(Structure from, int[] shape) throws IOException {
    StructureMembers sm = from.makeStructureMembers();
    for (Variable v : from.getVariables()) {
      Array data;
      if (v instanceof Sequence) {
        data = Array.factory(DataType.SEQUENCE, shape);  // an array sequence - one for each parent element
        //Structure s = (Structure) v;
        //StructureMembers smn = s.makeStructureMembers();
        // data = new ArraySequenceNested(smn, (int) Index.computeSize(v.getShapeAll())); // ??

      } else if (v instanceof Structure)
        data = ArrayStructureMA.factoryMA((Structure) v, combine(shape, v.getShape()));

      else
        data = Array.factory(v.getDataType(), combine(shape, v.getShape()));

      StructureMembers.Member m = sm.findMember(v.getShortName());
      m.setDataArray(data);
    }

    return new ArrayStructureMA(sm, shape);
  }

  static private int[] combine(int[] shape1, int[] shape2) {
    int[] result = new int[shape1.length + shape2.length];
    System.arraycopy(shape1, 0, result, 0, shape1.length);
    System.arraycopy(shape2, 0, result, shape1.length, shape2.length);
    return result;
  }

}


package ucar.nc2.ft2.simpgeometry;

import ucar.nc2.Attribute;
import ucar.nc2.Variable;
import ucar.nc2.constants.CF;
import ucar.nc2.dataset.NetcdfDataset;

/**
 * A class which given a dataset, will read from the dataset one of the simple geometry types. Those being polygon, line, or point.
 *
 * @author wchen@usgs.gov
 *
 */
public class SimpleGeometryReader {

	private NetcdfDataset ds;

	/**
	 * Returns a Polygon given a variable name and the geometric index. If the Polygon is not found it will return null. If the Polygon is a part of the Multi-Polygon, it will return the head
	 * (the first Polygon in the series which constitutes the Multi-Polygon).
	 *
	 * @param name of the variable which holds the Polygon
	 * @param index of the Polygon within the variable
	 * @return a new polygon with all associated information
	 */
	public Polygon readPolygon(String name, int index) {

		Variable polyvar = ds.findVariable(name);
		if(polyvar == null) return null;

		Polygon poly = null;

		// CFConvention
		if(ds.findGlobalAttribute(CF.CONVENTIONS) != null)
			if(ucar.nc2.dataset.conv.CF1Convention.getVersion(ds.findGlobalAttribute(CF.CONVENTIONS).getStringValue()) >= 8)
				poly = new CFPolygon();

		if(poly == null) return null;
		else return poly.setupPolygon(ds, polyvar, index);
	}

	/**
	 * Returns a Line given a variable name and the geometric index. If the Line is not found it will return null. If the Line is a part of the Multi-Line, it will return the head
	 * (the first Line in the series which constitutes the Multi-Line).
	 *
	 * @param name of the variable which holds the Line
	 * @param index of the Line within the variable
	 * @return a new line with all associated information
	 */
	public Line readLine(String name, int index) {

		Variable linevar = ds.findVariable(name);
		if(linevar == null) return null;
		Line line = null;

		// CFConvention
		if(ds.findGlobalAttribute(CF.CONVENTIONS) != null)
			if(ucar.nc2.dataset.conv.CF1Convention.getVersion(ds.findGlobalAttribute(CF.CONVENTIONS).getStringValue()) >= 8)
				line = new CFLine();

		if(line == null) return null;
		else return line.setupLine(ds, linevar, index);
	}

	/**
	 * Returns a Point given a variable name and the geometric index. If the Point is not found it will return null. If the Point is a part of the Multi-Point, it will return the head
	 * (the first Point in the series which constitutes the Multi-Point).
	 *
	 * @param name of the variable which holds the Point
	 * @param index of the Point within the variable
	 * @return a new Point with all associated information
	 */
	public Point readPoint(String name, int index) {

		Variable pointvar = ds.findVariable(name);
		if(pointvar == null) return null;
		Point pt = null;

		// CFConvention
		if(ds.findGlobalAttribute(CF.CONVENTIONS) != null)
			if(ucar.nc2.dataset.conv.CF1Convention.getVersion(ds.findGlobalAttribute(CF.CONVENTIONS).getStringValue()) >= 8)
				pt = new CFPoint();

		if(pt == null) return pt;
		else return pt.setupPoint(ds, pointvar, index);
	}

	/**
	 * Given a variable name, returns the geometry type which that variable is associated with.
	 * If the variable has no simple geometry information, null will be returned.
	 *
	 * @param name variable name which will have geometry type be checked
	 * @return Geometry Type if holds geometry information, null if not
	 */
	public GeometryType getGeometryType(String name) {
		Variable geometryVar = ds.findVariable(name);
		if(geometryVar == null) return null;

		// CFConvention
		if(ds.findGlobalAttribute(CF.CONVENTIONS) != null)
			if(ucar.nc2.dataset.conv.CF1Convention.getVersion(ds.findGlobalAttribute(CF.CONVENTIONS).getStringValue()) >= 8)
			{
				Attribute geometryTypeAttr = null;
				String geometry_type = null;

				geometryTypeAttr = geometryVar.findAttribute(CF.GEOMETRY_TYPE);
				if(geometryTypeAttr == null) return null;
				geometry_type = geometryTypeAttr.getStringValue();

				switch(geometry_type)
				{
					case CF.POLYGON:
						return GeometryType.POLYGON;
					case CF.LINE:
						return GeometryType.LINE;
					case CF.POINT:
						return GeometryType.POINT;
					default:
						return null;
				}
			}

		return null;
	}

	/**
	 * Constructs a new Simple Geometry Reader over the specified dataset.
	 *
	 * @param ds - the specified dataset
	 */
	public SimpleGeometryReader(NetcdfDataset ds) {
		this.ds = ds;
	}

}


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

package ucar.nc2.ft.point.standard;

import java.io.IOException;
import javax.annotation.Nonnull;

import ucar.ma2.StructureData;
import ucar.ma2.StructureDataIterator;
import ucar.nc2.ft.PointFeatureIterator;
import ucar.nc2.ft.StationTimeSeriesFeature;
import ucar.nc2.ft.point.StationFeature;
import ucar.nc2.ft.point.StationHelper;
import ucar.nc2.ft.point.StationTimeSeriesCollectionImpl;
import ucar.nc2.ft.point.StationTimeSeriesFeatureImpl;
import ucar.nc2.time.CalendarDateUnit;

/**
 * Object Heirarchy for StationFeatureCollection:
 * StationFeatureCollection (StandardStationCollectionImpl
 * PointFeatureCollectionIterator (anon)
 * StationFeature (StandardStationFeatureImpl)
 * PointFeatureIterator (StandardStationPointIterator)
 * PointFeatureImpl
 *
 * @author caron
 * @since Mar 28, 2008
 */
public class StandardStationCollectionImpl extends StationTimeSeriesCollectionImpl {
  private NestedTable ft;

  StandardStationCollectionImpl(NestedTable ft, CalendarDateUnit timeUnit, String altUnits) throws IOException {
    super(ft.getName(), timeUnit, altUnits);
    this.ft = ft;
    this.extras = ft.getExtras();
  }

  /**
   * Make a Station from the station data structure.
   *
   * @param stationData station data structure
   * @param recnum      station data recnum within table
   * @return Station or null, skip this Station
   */
  public StationTimeSeriesFeature makeStation(StructureData stationData, int recnum) {
    StationFeature s = ft.makeStation(stationData);
    if (s == null) return null;
    return new StandardStationFeatureImpl(s, timeUnit, stationData, recnum);
  }

  @Override
  protected StationHelper createStationHelper() throws IOException {
    StationHelper stationHelper = new StationHelper();

    try (StructureDataIterator siter = ft.getStationDataIterator()) {
      while (siter.hasNext()) {
        StructureData stationData = siter.next();
        StationTimeSeriesFeature stfs = makeStation(stationData, siter.getCurrentRecno());
        if (stfs != null) {
          stationHelper.addStation(stfs);
        }
      }
    }

    return stationHelper;
  }

  private class StandardStationFeatureImpl extends StationTimeSeriesFeatureImpl {
    int recnum;
    StructureData stationData;

    StandardStationFeatureImpl(StationFeature s, CalendarDateUnit dateUnit, StructureData stationData, int recnum) {
      super(s, dateUnit, StandardStationCollectionImpl.this.getAltUnits(), -1);
      this.recnum = recnum;
      this.stationData = stationData;
    }

    // an iterator over the observations for this station

    @Override
    public PointFeatureIterator getPointFeatureIterator() throws IOException {
      Cursor cursor = new Cursor(ft.getNumberOfLevels());
      cursor.recnum[1] = recnum;
      cursor.tableData[1] = stationData;
      cursor.currentIndex = 1;
      ft.addParentJoin(cursor); // there may be parent joins

      StructureDataIterator obsIter = ft.getLeafFeatureDataIterator(cursor);
      return new StandardPointFeatureIterator(this, ft, timeUnit, obsIter, cursor);
    }

    @Nonnull
    @Override
    public StructureData getFeatureData() {
      return stationData;
    }

  }
}


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

package dap4.core.ce;

import dap4.core.dmr.*;
import dap4.core.util.DapException;
import dap4.core.util.DapSort;
import dap4.core.util.Slice;

import java.util.ArrayDeque;
import java.util.Deque;

/**
 * Given an AST, compile it into a CEConstraint instance
 * Eventually this will go away and the constraint parser
 * will directly create the constraint.
 */

public class CECompiler
{
    protected Deque<DapVariable> scopestack = null;

    protected DapDataset dataset = null;

    protected CEConstraint ce = null; // will wrap view

    protected CEAST root = null;

    public CECompiler()
    {
    }

    public CEConstraint compile(DapDataset template, CEAST root)
            throws DapException
    {
        this.dataset = template;
        this.root = root;
        this.ce = new CEConstraint(this.dataset);
        this.scopestack = new ArrayDeque<DapVariable>();
        compileAST(root);
        return this.ce;
    }

    //////////////////////////////////////////////////
    // Accessors

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

    // Recursive AST walker; compilation of filters is done elsewhere.
    protected void
    compileAST(CEAST ast)
            throws DapException
    {
        switch (ast.sort) {
        case CONSTRAINT:
            for(CEAST clause : ast.clauses) {
                compileAST(clause);
            }
            // invoke semantic checks
            this.ce.expand();
            this.ce.finish();
            break;
        case PROJECTION:
            scopestack.clear();
            compileAST(ast.tree);
            break;
        case SEGMENT:
            compilesegment(ast);
            break;
        case SELECTION:
            scopestack.clear();
            compileselection(ast);
            break;
        case DEFINE:
            dimredef(ast);
            break;
        default:
            assert false : "uknown CEAST node type";
        }
    }

    protected void
    compileselection(CEAST ast)
            throws DapException
    {
        DapVariable var = compilesegment(ast.projection);
        if(!var.isSequence())
            throw new DapException("Attempt to apply a filter to a non-sequence variable: " + var.getFQN());
        // Convert field references in the filter
        // and canonicalize the comparisons
        compilefilter(var, (DapSequence)var.getBaseType(),ast.filter);
        // add filter
        ce.setFilter(var, ast.filter);
    }

    protected DapVariable
    compilesegment(CEAST ast)
            throws DapException
    {
        DapNode node = null;
        // Does this look like an fqn?
        if(isFQN(ast.name)) {
            DapDataset root = this.dataset;
            DapNode match = root.findByFQN(ast.name, DapSort.VARIABLE);
            if(match == null)
                throw new DapException("Undefined variable name: " + ast.name);
            node = match;
        } else { // interpret relative to parent or root
            DapNode parent = getParent();
            if(parent == null)
                parent = this.dataset;
            if(parent == null) {
                assert parent != null;
            }
            switch (parent.getSort()) {
            case DATASET:
                DapDataset dataset = (DapDataset) parent;
                node = dataset.findByName(ast.name, DapSort.VARIABLE);
                break;
            case VARIABLE:
                DapVariable v = (DapVariable)parent;
                DapType t = v.getBaseType();
                switch (t.getTypeSort()) {
                case Structure:
                    DapStructure struct = (DapStructure) t;
                    node = struct.findByName(ast.name);
                    break;
                case Sequence:
                    DapSequence seq = (DapSequence) t;
                    node = seq.findByName(ast.name);
                    break;
                default:
                    assert false : "Container cannot be atomic variable";
                }
            default:
                throw new DapException("relative names must be WRT to structure|dataset object: " + parent.getFQN());
            }
        }
        if(node == null) {
            throw new DapException("Constraint expression does not reference a known field: " + ast.name);
        }
        if(!(node instanceof DapVariable))
            throw new DapException("Attempt to use non-variable in projection: " + node.getFQN());
        DapVariable var = (DapVariable) node;
        ce.addVariable(var, ast.slices);
        scopestack.push(var);
        return var;
    }

    /**
     * Convert field references in a filter
     */
    public void
    compilefilter(DapVariable var, DapSequence seq, CEAST expr)
            throws DapException
    {
        if(expr == null)
            return;
        if(expr.sort == CEAST.Sort.SEGMENT) {
            // This must be a simple segment and it must appear in seq
            if(expr.subnodes != null)
                throw new DapException("compilefilter: Non-simple segment:" + expr.name);
            // Look for the name in the top-level field of seq
            DapVariable field = seq.findByName(expr.name);
            if(field == null)
                throw new DapException("compilefilter: Unknown filter variable:" + expr.name);
            expr.field = field;
        } else if(expr.sort == CEAST.Sort.EXPR) {
            if(expr.lhs != null)
                compilefilter(var, seq, expr.lhs);
            if(expr.rhs != null)
                compilefilter(var, seq, expr.rhs);
            // If both lhs and rhs are non-null,
            // canonicalize any comparison so that it is var op const
            if(expr.lhs != null && expr.rhs != null) {
                boolean leftvar = (expr.lhs.sort == CEAST.Sort.SEGMENT);
                boolean rightvar = (expr.rhs.sort == CEAST.Sort.SEGMENT);

                if(rightvar && !leftvar) { // swap operands
                    CEAST tmp = expr.lhs;
                    expr.lhs = expr.rhs;
                    expr.rhs = tmp;
                    // fix operator
                    switch (expr.op) {
                    case LT:  //x<y -> y>x
                        expr.op = CEAST.Operator.GT;
                        break;
                    case LE: //x<=y -> y>=x
                        expr.op = CEAST.Operator.GE;
                        break;
                    case GT:  //x>y -> y<x
                        expr.op = CEAST.Operator.LT;
                        break;
                    case GE:   //x>=y -> y<=x
                        expr.op = CEAST.Operator.LE;
                        break;
                    default:
                        break; // leave as is
                    }
                }
            }
        } else if(expr.sort == CEAST.Sort.CONSTANT) {
            return;
        } else
            throw new DapException("compilefilter: Unexpected node type:" + expr.sort);
    }

    /*
    // Create the necessary new dimension objects for a variable
    protected void
    createdimensions(DapVariable var, List<Slice> slices)
        throws DapException
    {
        List<DapDimension> dimset = var.getDimensions();
        int rank = dimset.size();
        assert rank == slices.size();
        Map<DapDimension, CEConstraint.Redef> redefs = ce.getNewDimensions();
        for(int i = 0;i < rank;i++) {
            DapDimension dim = dimset.get(i);
            Slice slice = slices.get(i);
            // See if this slice is "default" => use original or any redef
            if(slice.isDefault()) {
                // 1. See is this has been redefined
                CEConstraint.Redef redef = redefs.get(dim);
                if(newdim == null) {
                    // use the original dimension
                    ce.addDimension(dim);
                    dimset.add(dim);
                } else { // use the redef dimension
                    dimset.add(redef.newdim);
                }
            } else {// Specific slice was specified
                // Create an anonymous Dimension

            }
            slice.validate();
        }
    }  */

    // Process a dim redefinition
    protected void
    dimredef(CEAST node)
            throws DapException
    {
        DapDimension dim = (DapDimension) dataset.findByFQN(node.name, DapSort.DIMENSION);
        if(dim == null)
            throw new DapException("Constraint dim redef: no dimension name: " + node.name);
        Slice slice = node.slice;
        slice.finish();
        ce.addRedef(dim, slice);
    }

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

    protected DapVariable
    getParent()
    {
        if(scopestack.size() > 0)
            return scopestack.peek();
        return null;
    }

    static protected boolean
    isFQN(String s)
    {
        if(s == null || s.length() == 0)
            return false;
        return (s.charAt(0) == '/');
    }

}


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

/**
 * Concrete implementation of Array specialized for Objects.
 * Data storage is with 1D java array of Objects.
 *
 * @see Array
 * @author caron
 */
public class ArrayObject extends Array {

  /** package private. use Array.factory() */
  static ArrayObject factory(DataType dtype, Class elemType, boolean isVlen, Index index) {
    return ArrayObject.factory(dtype, elemType, isVlen, index, null);
  }

  /* Create new ArrayObject with given indexImpl and backing store.
   * Should be private.
   * @param index use this Index
   * @param stor. use this storage. if null, allocate.
   * @return. new ArrayObject.D<rank> or ArrayObject object.
   */
  static ArrayObject factory(DataType dtype, Class elemType, boolean isVlen, Index index, Object[] storage) {
      if (index instanceof Index0D) {
          return new ArrayObject.D0(dtype, elemType, isVlen, index, storage);
      } else if (index instanceof Index1D) {
          return new ArrayObject.D1(dtype, elemType, isVlen, index, storage);
      } else if (index instanceof Index2D) {
          return new ArrayObject.D2(dtype, elemType, isVlen, index, storage);
      } else if (index instanceof Index3D) {
          return new ArrayObject.D3(dtype, elemType, isVlen, index, storage);
      } else if (index instanceof Index4D) {
          return new ArrayObject.D4(dtype, elemType, isVlen, index, storage);
      } else if (index instanceof Index5D) {
          return new ArrayObject.D5(dtype, elemType, isVlen, index, storage);
      } else if (index instanceof Index6D) {
          return new ArrayObject.D6(dtype, elemType, isVlen, index, storage);
      } else if (index instanceof Index7D) {
          return new ArrayObject.D7(dtype, elemType, isVlen, index, storage);
      } else {
          return new ArrayObject(dtype, elemType, isVlen, index, storage);
      }
  }

  ///////////////////////////////////////////////////////////////////////////////
  protected final boolean isVlen;
  protected final Class elementType;
  protected final Object[] storage;

  /**
  * Create a new Array of type Object and the given shape.
  * dimensions.length determines the rank of the new Array.
  * @param elementType the type of element, eg String
  * @param shape the shape of the Array.
  */
  public ArrayObject(DataType dtype, Class elementType, boolean isVlen, int[] shape) {
    super(dtype, shape);
    this.elementType = elementType;
    storage = new Object[(int) indexCalc.getSize()];
    this.isVlen = isVlen;
  }

  /**
  * Create a new Array of type Object and the given shape and storage.
  * dimensions.length determines the rank of the new Array.
  * @param elementType the type of element, eg String
  * @param shape the shape of the Array.
  */
  public ArrayObject(DataType dtype, Class elementType, boolean isVlen, int [] shape, Object[] storage) {
    super(dtype, shape);
    this.elementType = elementType;
    this.storage = storage;
    this.isVlen = isVlen;
  }

 /**
  * Create a new Array using the given IndexArray and backing store.
  * used for sections, and factory. Trusted package private.
  * @param elementType the type of element, eg String
  * @param ima use this IndexArray as the index
  * @param data use this as the backing store. if null, allocate
  */
  ArrayObject(DataType dtype, Class elementType, boolean isVlen, Index ima, Object[] data) {
    super(dtype, ima);
    this.elementType = elementType;
    this.isVlen = isVlen;

    if (data != null) {
      storage = data;
    } else {
      storage = new Object[(int) indexCalc.getSize()];
    }
  }

  /** create new Array with given indexImpl and the same backing store */
  protected Array createView( Index index) {
    return ArrayObject.factory( dataType, elementType, isVlen, index, storage);
  }

  @Override
  public Array copy() {
    Array newA = factory(getDataType(), getElementType(), isVlen(), Index.factory(getShape()));
    MAMath.copy(newA, this);
    return newA;
  }

  /** Get underlying primitive array storage. CAUTION! You may invalidate your warrentee! */
  public Object getStorage() { return storage; }

  // copy from javaArray to storage using the iterator: used by factory( Object);
  protected void copyFrom1DJavaArray(IndexIterator iter, Object javaArray) {
    Object[] ja = (Object []) javaArray;
    for (Object aJa : ja) iter.setObjectNext(aJa);
  }

  // copy to javaArray from storage using the iterator: used by copyToNDJavaArray;
  protected void copyTo1DJavaArray(IndexIterator iter, Object javaArray) {
    Object[] ja = (Object []) javaArray;
    for (int i=0; i<ja.length; i++)
      ja[i] = iter.getObjectNext();
  }

 /** Return the element class type */
 @Override
  public Class getElementType() { return elementType; }

  @Override
  public boolean isVlen() {
    return isVlen;
  }

  /** not legal, throw ForbiddenConversionException */
  public double getDouble(Index i) { throw new ForbiddenConversionException(); }
  /** not legal, throw ForbiddenConversionException */
  public void setDouble(Index i, double value) { throw new ForbiddenConversionException(); }

  /** not legal, throw ForbiddenConversionException */
  public float getFloat(Index i) { throw new ForbiddenConversionException(); }
  /** not legal, throw ForbiddenConversionException */
  public void setFloat(Index i, float value) { throw new ForbiddenConversionException(); }

  /** not legal, throw ForbiddenConversionException */
  public long getLong(Index i) { throw new ForbiddenConversionException(); }
  /** not legal, throw ForbiddenConversionException */
  public void setLong(Index i, long value) { throw new ForbiddenConversionException(); }

  /** not legal, throw ForbiddenConversionException */
  public int getInt(Index i) { throw new ForbiddenConversionException(); }
  /** not legal, throw ForbiddenConversionException */
  public void setInt(Index i, int value) { throw new ForbiddenConversionException(); }

  /** not legal, throw ForbiddenConversionException */
  public short getShort(Index i) { throw new ForbiddenConversionException(); }
  /** not legal, throw ForbiddenConversionException */
  public void setShort(Index i, short value) { throw new ForbiddenConversionException(); }

  /** not legal, throw ForbiddenConversionException */
  public byte getByte(Index i) { throw new ForbiddenConversionException(); }
  /** not legal, throw ForbiddenConversionException */
  public void setByte(Index i, byte value) { throw new ForbiddenConversionException(); }

  /** not legal, throw ForbiddenConversionException */
  public boolean getBoolean(Index i) { throw new ForbiddenConversionException(); }
  /** not legal, throw ForbiddenConversionException */
  public void setBoolean(Index i, boolean value) { throw new ForbiddenConversionException(); }

  /** not legal, throw ForbiddenConversionException */
  public char getChar(Index i) { throw new ForbiddenConversionException(); }
  /** not legal, throw ForbiddenConversionException */
  public void setChar(Index i, char value) { throw new ForbiddenConversionException(); }

  public Object getObject(Index i) {
    return storage[i.currentElement()];
  }
  public void setObject(Index i, Object value) {
    storage[i.currentElement()] = value;
  }

    // trusted, assumes that individual dimension lengths have been checked
    // package private : mostly for iterators
  public double getDouble(int index) {throw new ForbiddenConversionException(); }
  public void setDouble(int index, double value) { throw new ForbiddenConversionException(); }

  public float getFloat(int index) { throw new ForbiddenConversionException(); }
  public void setFloat(int index, float value) { throw new ForbiddenConversionException(); }

  public long getLong(int index) {throw new ForbiddenConversionException(); }
  public void setLong(int index, long value) { throw new ForbiddenConversionException(); }

  public int getInt(int index) { throw new ForbiddenConversionException(); }
  public void setInt(int index, int value) { throw new ForbiddenConversionException(); }

  public short getShort(int index) { throw new ForbiddenConversionException(); }
  public void setShort(int index, short value) { throw new ForbiddenConversionException(); }

  public byte getByte(int index) { throw new ForbiddenConversionException(); }
  public void setByte(int index, byte value) {throw new ForbiddenConversionException(); }

  public char getChar(int index) { throw new ForbiddenConversionException(); }
  public void setChar(int index, char value) { throw new ForbiddenConversionException(); }

  public boolean getBoolean(int index) { throw new ForbiddenConversionException(); }
  public void setBoolean(int index, boolean value) { throw new ForbiddenConversionException(); }

  public Object getObject(int index) { return storage[index]; }
  public void setObject(int index, Object value) { storage[index] = value; }

  /** Concrete implementation of Array specialized for Objects, rank 0. */
  public static class D0 extends ArrayObject {
    private Index0D ix;
    /** Constructor. */
    public D0 (DataType dtype, Class elemType, boolean isVlen) {
      super(dtype, elemType, isVlen, new int [] {});
      ix = (Index0D) indexCalc;
    }
    private D0 (DataType dtype, Class elemType, boolean isVlen, Index i, Object[] store) {
      super(dtype, elemType, isVlen, i, store);
      ix = (Index0D) indexCalc;
    }
    /** get the value. */
    public Object get() {
      return storage[ix.currentElement()];
    }
    /** set the value. */
    public void set(Object value) {
      storage[ix.currentElement()] = value;
    }
  }

  /** Concrete implementation of Array specialized for Objects, rank 1. */
  public static class D1 extends ArrayObject {
    private Index1D ix;
    /** Constructor for array of shape {len0}. */
    public D1 (DataType dtype, Class elemType, boolean isVlen, int len0) {
      super(dtype, elemType, isVlen, new int [] {len0});
      ix = (Index1D) indexCalc;
    }
    private D1 (DataType dtype, Class elemType, boolean isVlen, Index i, Object[] store) {
      super(dtype, elemType, isVlen, i, store);
      ix = (Index1D) indexCalc;
    }
    /** get the value. */
    public Object get(int i) {
      return storage[ix.setDirect(i)];
    }
    /** set the value. */
    public void set(int i, Object value) {
      storage[ix.setDirect(i)] = value;
    }
  }

  /** Concrete implementation of Array specialized for Objects, rank 2. */
  public static class D2 extends ArrayObject {
    private Index2D ix;
    /** Constructor for array of shape {len0,len1}. */
    public D2 (DataType dtype, Class elemType, boolean isVlen, int len0, int len1) {
      super(dtype, elemType, isVlen, new int [] {len0, len1});
      ix = (Index2D) indexCalc;
    }
    private D2 (DataType dtype, Class elemType, boolean isVlen, Index i, Object[] store) {
      super(dtype, elemType, isVlen, i, store);
      ix = (Index2D) indexCalc;
    }
    /** get the value. */
    public Object get(int i, int j) {
      return storage[ix.setDirect(i,j)];
    }
    /** set the value. */
    public void set(int i, int j, Object value) {
      storage[ix.setDirect(i,j)] = value;
    }
  }

  /** Concrete implementation of Array specialized for Objects, rank 3. */
  public static class D3 extends ArrayObject {
    private Index3D ix;
    /** Constructor for array of shape {len0,len1,len2}. */
    public D3 (DataType dtype, Class elemType, boolean isVlen, int len0, int len1, int len2) {
      super(dtype, elemType, isVlen, new int [] {len0, len1, len2});
      ix = (Index3D) indexCalc;
    }
    private D3 (DataType dtype, Class elemType, boolean isVlen, Index i, Object[] store) {
      super(dtype, elemType, isVlen, i, store);
      ix = (Index3D) indexCalc;
    }
    /** get the value. */
    public Object get(int i, int j, int k) {
      return storage[ix.setDirect(i,j,k)];
    }
    /** set the value. */
    public void set(int i, int j, int k, Object value) {
      storage[ix.setDirect(i,j,k)] = value;
    }
  }

  /** Concrete implementation of Array specialized for Objects, rank 4. */
  public static class D4 extends ArrayObject {
    private Index4D ix;
    /** Constructor for array of shape {len0,len1,len2,len3}. */
    public D4 (DataType dtype, Class elemType, boolean isVlen, int len0, int len1, int len2, int len3) {
      super(dtype, elemType, isVlen, new int [] {len0, len1, len2, len3});
      ix = (Index4D) indexCalc;
    }
    private D4 (DataType dtype, Class elemType, boolean isVlen, Index i, Object[] store) {
      super(dtype, elemType, isVlen, i, store);
      ix = (Index4D) indexCalc;
    }
    /** get the value. */
    public Object get(int i, int j, int k, int l) {
      return storage[ix.setDirect(i,j,k,l)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, Object value) {
      storage[ix.setDirect(i,j,k,l)] = value;
    }
  }

  /** Concrete implementation of Array specialized for Objects, rank 5. */
  public static class D5 extends ArrayObject {
    private Index5D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4}. */
    public D5 (DataType dtype, Class elemType, boolean isVlen, int len0, int len1, int len2, int len3, int len4) {
      super(dtype, elemType, isVlen, new int [] {len0, len1, len2, len3, len4});
      ix = (Index5D) indexCalc;
    }
    private D5 (DataType dtype, Class elemType, boolean isVlen, Index i, Object[] store) {
      super(dtype, elemType, isVlen, i, store);
      ix = (Index5D) indexCalc;
    }
    /** get the value. */
    public Object get(int i, int j, int k, int l, int m) {
      return storage[ix.setDirect(i,j,k,l, m)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, Object value) {
      storage[ix.setDirect(i,j,k,l, m)] = value;
    }
  }

  /** Concrete implementation of Array specialized for Objects, rank 6. */
  public static class D6 extends ArrayObject {
    private Index6D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4,len5,}. */
    public D6 (DataType dtype, Class elemType, boolean isVlen, int len0, int len1, int len2, int len3, int len4, int len5) {
      super(dtype, elemType, isVlen, new int [] {len0, len1, len2, len3, len4, len5});
      ix = (Index6D) indexCalc;
    }
    private D6 (DataType dtype, Class elemType, boolean isVlen, Index i, Object[] store) {
      super(dtype, elemType, isVlen, i, store);
      ix = (Index6D) indexCalc;
    }
    /** get the value. */
    public Object get(int i, int j, int k, int l, int m, int n) {
      return storage[ix.setDirect(i,j,k,l,m,n)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, int n, Object value) {
      storage[ix.setDirect(i,j,k,l,m,n)] = value;
    }
  }

  /** Concrete implementation of Array specialized for Objects, rank 7. */
  public static class D7 extends ArrayObject {
    private Index7D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4,len5,len6}. */
    public D7 (DataType dtype, Class elemType, boolean isVlen, int len0, int len1, int len2, int len3, int len4, int len5, int len6) {
      super(dtype, elemType, isVlen, new int [] {len0, len1, len2, len3, len4, len5, len6});
      ix = (Index7D) indexCalc;
    }
    private D7 (DataType dtype, Class elemType, boolean isVlen, Index i, Object[] store) {
      super(dtype, elemType, isVlen, i, store);
      ix = (Index7D) indexCalc;
    }
    /** get the value. */
    public Object get(int i, int j, int k, int l, int m, int n, int o) {
      return storage[ix.setDirect(i,j,k,l,m,n,o)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, int n, int o, Object value) {
      storage[ix.setDirect(i,j,k,l,m,n,o)] = value;
    }
  }

}