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

package dap4.cdm;

import dap4.core.dmr.DapEnumeration;
import dap4.core.dmr.DapType;
import dap4.core.dmr.TypeSort;
import dap4.core.util.ConversionException;
import dap4.core.util.CoreTypeFcns;
import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import ucar.ma2.Array;
import ucar.ma2.DataType;
import ucar.ma2.ForbiddenConversionException;
import ucar.nc2.EnumTypedef;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * This Class is used to isolate as many as possible
 * of the switch statements using TypeSort enums
 * (Or in somecase DapType.getTypeSort())
 * Singleton
 */

abstract public class CDMTypeFcns
{
    //////////////////////////////////////////////////
    // Constants

    // Big integer representation of 0xFFFFFFFFFFFFFFFF
    static final BigInteger LONGMASK = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE);

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

    /* Needed to implement Array.getElement() */
    static public Class
    cdmElementClass(DataType dt)
    {
        switch (dt) {
        case BOOLEAN:
            return boolean.class;
        case ENUM1:
        case BYTE:
            return byte.class;
        case CHAR:
            return char.class;
        case ENUM2:
        case SHORT:
            return short.class;
        case ENUM4:
        case INT:
            return int.class;
        case LONG:
            return long.class;
        case FLOAT:
            return float.class;
        case DOUBLE:
            return double.class;
        case STRING:
            return String.class;
        case OPAQUE:
            return ByteBuffer.class;
        case UBYTE:
            return byte.class;
        case USHORT:
            return short.class;
        case UINT:
            return int.class;
        case ULONG:
            return long.class;
        default:
            break;
        }
        return null;
    }

    static public Object
    createVector(DataType type, long count)
    {
        int icount = (int) count;
        Object vector = null;
        switch (type) {
        case BOOLEAN:
            vector = new boolean[icount];
            break;
        case CHAR:
            vector = new char[icount];
            break;
        case ENUM1:
        case UBYTE:
        case BYTE:
            vector = new byte[icount];
            break;
        case ENUM2:
            ;
        case SHORT:
        case USHORT:
            vector = new short[icount];
            break;
        case ENUM4:
        case INT:
        case UINT:
            vector = new int[icount];
            break;
        case LONG:
        case ULONG:
            vector = new long[icount];
            break;
        case FLOAT:
            vector = new float[icount];
            break;
        case DOUBLE:
            vector = new double[icount];
            break;
        case STRING:
            vector = new String[icount];
            break;
        case OPAQUE:
            vector = new ByteBuffer[icount];
            break;
        default:
            throw new ForbiddenConversionException();
        }
        return vector;
    }

    static public Object
    createVector(DapType type, long count)
    {
        if(type.getAtomicType() == TypeSort.Enum)
            return createVector(((DapEnumeration) type).getBaseType(), count);
        return CoreTypeFcns.createVector(type.getTypeSort(), count);
    }

    static public DataType
    enumTypeFor(DapType type)
    {
        switch (type.getTypeSort()) {
        case Char:
        case Int8:
        case UInt8:
            return DataType.ENUM1;
        case Int16:
        case UInt16:
            return DataType.ENUM2;
        case Int32:
        case UInt32:
            return DataType.ENUM4;
        case Int64:
        case UInt64:
            // Unfortunately, CDM does not support (U)Int64, so truncate to 32.
            return DataType.ENUM4;
        case Enum:
            //Coverity[FB.BC_UNCONFIRMED_CAST]
            return CDMTypeFcns.enumTypeFor(((DapEnumeration) type).getBaseType());
        default:
            break;
        }
        return null;
    }

    static public DapType
    cdmtype2daptype(DataType datatype)
    {
        switch (datatype) {
        case CHAR:
            return DapType.CHAR;
        case BYTE:
            return DapType.INT8;
        case SHORT:
            return DapType.INT16;
        case INT:
            return DapType.INT32;
        case LONG:
            return DapType.INT64;
        case UBYTE:
            return DapType.UINT8;
        case USHORT:
            return DapType.UINT16;
        case UINT:
            return DapType.UINT32;
        case ULONG:
            return DapType.UINT64;
        case FLOAT:
            return DapType.FLOAT32;
        case DOUBLE:
            return DapType.FLOAT64;
        case STRING:
            return DapType.STRING;
        case OPAQUE:
            return DapType.OPAQUE;

        // For these, return the integer basetype
        case ENUM1:
            return DapType.INT8;
        case ENUM2:
            return DapType.INT16;
        case ENUM4:
            return DapType.INT32;

        // Undefined
        case SEQUENCE:
        case STRUCTURE:
        default:
            break;
        }
        return null;
    }

    static public DataType
    daptype2cdmtype(DapType type)
    {
        assert (type != null);
        switch (type.getTypeSort()) {
        case Char:
            return DataType.CHAR;
        case UInt8:
            return DataType.UBYTE;
        case Int8:
            return DataType.BYTE;
        case Int16:
            return DataType.SHORT;
        case UInt16:
            return DataType.USHORT;
        case Int32:
            return DataType.INT;
        case UInt32:
            return DataType.UINT;
        case Int64:
            return DataType.LONG;
        case UInt64:
            return DataType.ULONG;
        case Float32:
            return DataType.FLOAT;
        case Float64:
            return DataType.DOUBLE;
        case String:
        case URL:
            return DataType.STRING;
        case Opaque:
            return DataType.OPAQUE;
        case Enum:
            //Coverity[FB.BC_UNCONFIRMED_CAST]
            DapEnumeration dapenum = (DapEnumeration) type;
            switch (dapenum.getBaseType().getTypeSort()) {
            case Char:
            case UInt8:
            case Int8:
                return DataType.ENUM1;
            case Int16:
            case UInt16:
                return DataType.ENUM2;
            case Int32:
            case UInt32:
                return DataType.ENUM4;
            case Int64:
            case UInt64:
                // since there is no ENUM8, use ENUM4
                return DataType.ENUM4;
            default:
                break;
            }
            break;
        case Structure:
            return DataType.STRUCTURE;
        case Sequence:
            return DataType.SEQUENCE;
        default:
            break;
        }
        return null;
    }

    /**
     * Conmpute the size, in databuffer,
     * of the daptype wrt to a serialization;
     * 0 if undefined.
     *
     * @param atomtype The type of interest
     * @return the size, in databuffer
     */
    static public int
    daptypeSize(TypeSort atomtype)
    {
        switch (atomtype) {
        case Char: // remember serial size is 1, not 2.
        case UInt8:
        case Int8:
            return 1;
        case Int16:
        case UInt16:
            return 2;
        case Int32:
        case UInt32:
        case Float32:
            return 4;
        case Int64:
        case UInt64:
        case Float64:
            return 8;
        default:
            break;
        }
        return 0;
    }

    static public long
    extract(TypeSort sort, Object value)
    {
        long lvalue = 0;
        switch (sort) {
        case Int8:
            lvalue = (long) ((Byte) value).byteValue();
            break;
        case Char:
        case UInt8:
            lvalue = (long) ((Byte) value).byteValue();
            lvalue = lvalue & 0xFFL;
            break;
        case Int16:
            lvalue = (long) ((Short) value).shortValue();
            break;
        case UInt16:
            lvalue = (long) ((Short) value).shortValue();
            lvalue = lvalue & 0xFFFFL;
            break;
        case Int32:
            lvalue = (long) ((Integer) value).intValue();
            break;
        case UInt32:
            lvalue = (long) ((Integer) value).intValue();
            lvalue = lvalue & 0xFFFFFFFFL;
            break;
        case Int64:
        case UInt64:
            lvalue = ((Long) value).longValue();
            break;
        case Float32:
            lvalue = (long) ((Float) value).floatValue();
            break;
        case Float64:
            lvalue = (long) ((Double) value).doubleValue();
            break;
        default:
            throw new ForbiddenConversionException("Type not convertible to long");
        }
        return lvalue;
    }

    static public Object
    convert(TypeSort dstsort, TypeSort srcsort, Object src)
    {
        Object result = null;
        long lval;
        boolean ok = true;
        int len = 0;
        char[] csrc;
        byte[] bsrc;
        short[] shsrc;
        int[] isrc;
        long[] lsrc;
        float[] fsrc;
        double[] dsrc;
        String[] ssrc;
        char[] cresult;
        byte[] bresult;
        short[] shresult;
        int[] iresult;
        long[] lresult;
        float[] fresult;
        double[] dresult;
        String[] sresult;
        BigInteger bi;
        boolean srcunsigned = srcsort.isUnsigned();
        boolean dstunsigned = dstsort.isUnsigned();

        try {
            // Do a double switch src X dst (ugh!)
            done:
            switch (srcsort) {

            case Char: //Char->
                csrc = (char[]) src;
                len = csrc.length;
                switch (dstsort) {
                case Char: //char->char
                case Int8: //char->int8
                case UInt8: //char->uint8
                    return src;
                case Int16: //char->Int16
                case UInt16://char->UInt16;
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) (((int) csrc[i]) & 0xFF);
                    }
                    break;
                case Int32: //char->Int32
                case UInt32://char->UInt32;
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        iresult[i] = (int) (((int) csrc[i]) & 0xFF);
                    }
                    break;
                case Int64: //char->Int64
                case UInt64://char->UInt64;
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        lresult[i] = (long) (((int) csrc[i]) & 0xFF);
                    }
                    break;
                case Float32:
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) (((int) csrc[i]) & 0xFF);
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) (((int) csrc[i]) & 0xFF);
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case Int8: //Int8->
                bsrc = (byte[]) src;
                len = bsrc.length;
                switch (dstsort) {
                case Char: //int8->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (((int) bsrc[i]) & 0xFF);
                    }
                    break;
                case Int16: //int8->Int16
                case UInt16://int8->UInt16;
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) bsrc[i];
                    }
                    if(dstunsigned) {
                        for(int i = 0; i < len; i++) {
                            shresult[i] &= (short) 0xFF;
                        }
                    }
                    break;
                case Int32: //int8->Int32
                case UInt32://int8->UInt32;
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        iresult[i] = (int) bsrc[i];
                    }
                    if(dstunsigned) {
                        for(int i = 0; i < len; i++) {
                            iresult[i] &= 0xFF;
                        }
                    }
                    break;
                case Int64: //int8->Int64
                case UInt64://int8->UInt64;
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        lresult[i] = (long) bsrc[i];
                    }
                    if(dstunsigned) {
                        for(int i = 0; i < len; i++) {
                            lresult[i] &= 0xFFL;
                        }
                    }
                    break;
                case Float32: //int8->float
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) bsrc[i];
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) bsrc[i];
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case UInt8: //UInt8->
                bsrc = (byte[]) src;
                len = bsrc.length;
                switch (dstsort) {
                case Char: //Byte->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (((int) bsrc[i]) & 0xFF);
                    }
                    break;
                case Int16: //Byte->Int16
                case UInt16://Byte->UInt16;
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) (((int) bsrc[i]) & 0xFF);
                    }
                    break;
                case Int32: //Byte->Int32
                case UInt32://Byte->UInt32;
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        iresult[i] = ((int) bsrc[i]) & 0xFF;
                    }
                    break;
                case Int64: //Byte->Int64
                case UInt64://Byte->UInt64;
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        lresult[i] = ((long) bsrc[i]) & 0xFFL;
                    }
                    break;
                case Float32: //Byte->float
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) ((int) bsrc[i] & 0xFF);
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) ((int) bsrc[i] & 0xFF);
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case Int16: //Int16->
                shsrc = (short[]) src;
                len = shsrc.length;
                switch (dstsort) {
                case Char: //int16->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (((int) shsrc[i]) & 0xFF);
                    }
                    break;
                case Int8: //int16->Int8
                case UInt8://int16->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        bresult[i] = (byte) shsrc[i];
                    }
                    break;
                case Int32: //int16->Int32
                case UInt32://int16->UInt32;
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        iresult[i] = (int) shsrc[i];
                    }
                    if(dstunsigned) {
                        for(int i = 0; i < len; i++) {
                            iresult[i] &= 0xFFFF;
                        }
                    }
                    break;
                case Int64: //int16->Int64
                case UInt64://int16->UInt64;
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        lresult[i] = (long) shsrc[i];
                    }
                    if(dstunsigned) {
                        for(int i = 0; i < len; i++) {
                            lresult[i] &= 0xFFFFL;
                        }
                    }
                    break;
                case Float32: //int16->float
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) shsrc[i];
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) shsrc[i];
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case UInt16: //UInt16->
                shsrc = (short[]) src;
                len = shsrc.length;
                switch (dstsort) {
                case Char: //UInt16->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (((int) shsrc[i]) & 0xFF);
                    }
                    break;
                case Int8: //UInt16->Int8
                case UInt8://UInt16->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        bresult[i] = (byte) shsrc[i];
                    }
                    break;
                case Int32: //UInt16->Int32
                case UInt32://UInt16->UInt32;
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        iresult[i] = ((int) shsrc[i]) & 0xFFFF;
                    }
                    break;
                case Int64: //UInt16->Int64
                case UInt64://UInt16->UInt64;
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        lresult[i] = ((long) shsrc[i]) & 0xFFFFL;
                    }
                    break;
                case Float32: //UInt16->float
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) ((int) shsrc[i] & 0xFFFF);
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) ((int) shsrc[i] & 0xFFFF);
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case Int32: //Int32->
                isrc = (int[]) src;
                len = isrc.length;
                switch (dstsort) {
                case Char: //int32->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (isrc[i] & 0xFF);
                    }
                    break;
                case Int8: //Int32->Int8
                case UInt8://Int32->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        bresult[i] = (byte) isrc[i];
                    }
                    break;
                case Int16: //Int32->Int16
                case UInt16://Int32->UInt16;
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) isrc[i];
                    }
                    break;
                case Int64: //Int32->Int64
                case UInt64://Int32->UInt64
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        lresult[i] = (long) isrc[i];
                    }
                    if(dstunsigned) {
                        for(int i = 0; i < len; i++) {
                            lresult[i] &= 0xFFFFL;
                        }
                    }
                    break;
                case Float32: //int32->float
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) isrc[i];
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) isrc[i];
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case UInt32: //UInt32->
                isrc = (int[]) src;
                len = isrc.length;
                switch (dstsort) {
                case Char: //UInt32->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (((int) isrc[i]) & 0xFF);
                    }
                    break;
                case Int8: //Int32->Int8
                case UInt8://UInt32->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        bresult[i] = (byte) isrc[i];
                    }
                    break;
                case Int16: //Int32->Int16
                case UInt16://UInt32->UInt16
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) isrc[i];
                    }
                    break;
                case Int64: //Int32->Int64
                case UInt64://UInt32->UInt64;
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        lresult[i] = (long) isrc[i];
                    }
                    if(dstunsigned) {
                        for(int i = 0; i < len; i++) {
                            lresult[i] &= 0xFFFFFFFFL;
                        }
                    }
                    break;
                case Float32: //UInt32->float
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) ((int) isrc[i] & 0xFFFF);
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) ((int) isrc[i] & 0xFFFF);
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case Int64: //Int64->
                lsrc = (long[]) src;
                len = lsrc.length;
                switch (dstsort) {
                case Char: //Int64->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (lsrc[i] & 0xFF);
                    }
                    break;
                case Int8: //Int64->Int8
                case UInt8://Int64->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        bresult[i] = (byte) lsrc[i];
                    }
                    break;
                case Int16: //Int64->Int16
                case UInt16://Int64->UInt16;
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) lsrc[i];
                    }
                    break;
                case Int32: //Int64->Int32
                case UInt32://Int64->UInt32;
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        iresult[i] = (int) lsrc[i];
                    }
                    break;
                case Float32: //Int64->float
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) lsrc[i];
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) lsrc[i];
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case UInt64: //UInt64->
                lsrc = (long[]) src;
                len = lsrc.length;
                switch (dstsort) {
                case Char: //UInt64->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (lsrc[i] & 0xFFL);
                    }
                    break;
                case Int8: //Int64->Int8
                case UInt8://UInt64->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        bresult[i] = (byte) lsrc[i];
                    }
                    break;
                case Int16: //Int64->Int16
                case UInt16://UInt64->UInt16
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) lsrc[i];
                    }
                    break;
                case Int32: //Int64->Int32
                case UInt32://UInt64->UInt32
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        iresult[i] = (int) lsrc[i];
                    }
                    break;
                case Float32: //UInt64->float
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        bi = BigInteger.valueOf(lsrc[i]);
                        bi = bi.and(DapUtil.BIG_UMASK64);
                        fresult[i] = bi.floatValue();
                    }
                    break;
                case Float64:
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        bi = BigInteger.valueOf(lsrc[i]);
                        bi = bi.and(DapUtil.BIG_UMASK64);
                        dresult[i] = bi.doubleValue();
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case Float32: //Float32->
                fsrc = (float[]) src;
                len = fsrc.length;
                switch (dstsort) {
                case Char: //Float32->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (((int) fsrc[i]) & 0xFF);
                    }
                    break;
                case Int8: //Float32->Int8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        bresult[i] = (byte) fsrc[i];
                    }
                    break;
                case UInt8://Float32->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        if(fsrc[i] < 0) {
                            ok = false;
                            break done;
                        }
                        bresult[i] = (byte) fsrc[i];
                    }
                    break;
                case Int16: //Float32->Int16
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) fsrc[i];
                    }
                    break;
                case UInt16://Float32->UInt16
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        if(fsrc[i] < 0) {
                            ok = false;
                            break done;
                        }
                        ;
                        shresult[i] = (short) fsrc[i];
                    }
                    break;
                case Int32: //Float32->Int32
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        if(fsrc[i] < 0) {
                            ok = false;
                            break done;
                        }
                        iresult[i] = (int) fsrc[i];
                    }
                    break;
                case UInt32://Float32->UInt32
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        if(fsrc[i] < 0) {
                            ok = false;
                            break done;
                        }
                        iresult[i] = (int) fsrc[i];
                    }
                    break;
                case Int64: //Float32->Int64
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        BigDecimal bd = new BigDecimal(fsrc[i]);
                        lresult[i] = bd.toBigInteger().longValue();
                    }
                    break;
                case UInt64://Float32->UInt64
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        if(fsrc[i] < 0) {
                            ok = false;
                            break done;
                        } // not convertible
                        BigDecimal bd = new BigDecimal(fsrc[i]);
                        lresult[i] = bd.toBigInteger().longValue();
                    }
                    break;
                case Float64://Float32->Float64
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = (double) fsrc[i];
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;
            case Float64: //Float64->
                dsrc = (double[]) src;
                len = dsrc.length;
                switch (dstsort) {
                case Char: //Float64->char
                    result = (cresult = new char[len]);
                    for(int i = 0; i < len; i++) {
                        cresult[i] = (char) (((int) dsrc[i]) & 0xFF);
                    }
                    break;
                case Int8: //Float64->Int8
                case UInt8://Float64->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        bresult[i] = (byte) dsrc[i];
                    }
                    break;
                case Int16: //Float64->Int16
                case UInt16://Float64->UInt16
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        shresult[i] = (short) dsrc[i];
                    }
                    break;
                case Int32: //Float64->Int32
                case UInt32://Float64->UInt32
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        iresult[i] = (int) dsrc[i];
                    }
                    break;
                case Int64: //Float64->Int64
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        BigDecimal bd = new BigDecimal(dsrc[i]);
                        lresult[i] = bd.toBigInteger().longValue();
                    }
                    break;
                case UInt64://Float64->UInt64
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        if(dsrc[i] < 0) {
                            ok = false;
                            break done;
                        } // not convertible
                        BigDecimal bd = new BigDecimal(dsrc[i]);
                        lresult[i] = bd.toBigInteger().longValue();
                    }
                    break;
                case Float32://Float32->Float64
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = (float) dsrc[i];
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;

            case String: //String->
                ssrc = (String[]) src;
                len = ssrc.length;
                switch (dstsort) {
                case Char: //String->char is not defined
                    ok = false;
                    break done;
                case Int8: //String->Int8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        lval = Long.decode(ssrc[i]);
                        if(lval < -128L || lval > 127L) {
                            ok = false;
                            break done;
                        }
                        bresult[i] = (byte) lval;
                    }
                    break;
                case UInt8://String->UInt8
                    result = (bresult = new byte[len]);
                    for(int i = 0; i < len; i++) {
                        lval = Long.decode(ssrc[i]);
                        if(lval < 0 || lval > 255L) {
                            ok = false;
                            break done;
                        }
                        bresult[i] = (byte) lval;
                    }
                    break;
                case Int16: //String->Int16
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        lval = Long.decode(ssrc[i]);
                        if(lval < -32768L || lval > 32767L) {
                            ok = false;
                            break done;
                        }
                        shresult[i] = (short) lval;
                    }
                    break;
                case UInt16://String->UInt16
                    result = (shresult = new short[len]);
                    for(int i = 0; i < len; i++) {
                        lval = Long.decode(ssrc[i]);
                        if(lval < 0 || lval > 65535L) {
                            ok = false;
                            break done;
                        }
                        shresult[i] = (short) lval;
                    }
                    break;
                case Int32: //String->Int32
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        lval = Long.decode(ssrc[i]);
                        if(lval < -2147483648L || lval > 2147483647L) {
                            ok = false;
                            break done;
                        }
                        iresult[i] = (int) lval;
                    }
                    break;
                case UInt32://String->UInt32
                    result = (iresult = new int[len]);
                    for(int i = 0; i < len; i++) {
                        lval = Long.decode(ssrc[i]);
                        if(lval < 0 || lval > 4294967295L) {
                            ok = false;
                            break done;
                        }
                        iresult[i] = (int) lval;
                    }
                    break;
                case Int64: //String->Int64
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        bi = new BigInteger(ssrc[i]);
                        try {
                            lval = bi.longValueExact();
                        } catch (ArithmeticException ae) {
                            ok = false;
                            break done;
                        }
                        lresult[i] = lval;
                    }
                    break;
                case UInt64://String->UInt64
                    result = (lresult = new long[len]);
                    for(int i = 0; i < len; i++) {
                        bi = new BigInteger(ssrc[i]);
                        if(bi.compareTo(BigInteger.ZERO) < 0) {
                            ok = false;
                            break done;
                        }
                        if(bi.compareTo(LONGMASK) > 0) {
                            ok = false;
                            break done;
                        }
                        lval = bi.longValue();
                        lresult[i] = lval;
                    }
                    break;
                case Float32://String->Float32
                    result = (fresult = new float[len]);
                    for(int i = 0; i < len; i++) {
                        fresult[i] = Float.parseFloat(ssrc[i]);
                    }
                    break;
                case Float64://String->Float64
                    result = (dresult = new double[len]);
                    for(int i = 0; i < len; i++) {
                        dresult[i] = Double.parseDouble(ssrc[i]);
                    }
                    break;
                default:
                    ok = false;
                    break done;
                }
                break;

            default:
                ok = false;
                break done;
            }
        } catch (NumberFormatException nfe) {
            ok = false;
        }
        if(!ok)
            throw new ForbiddenConversionException(srcsort.name() + "->" + dstsort.name());
        return result;
    }

    static public void
    vectorcopy(DapType datatype, Object src, Object dst, long srcoffset, long dstoffset)
            throws DapException
    {
        switch (datatype.getTypeSort()) {
        case UInt8:
        case Int8:
            ((byte[]) dst)[(int) dstoffset] = ((byte[]) src)[(int) srcoffset];
            break;
        case Char:
            ((char[]) dst)[(int) dstoffset] = ((char[]) src)[(int) srcoffset];
            break;
        case UInt16:
        case Int16:
            ((short[]) dst)[(int) dstoffset] = ((short[]) src)[(int) srcoffset];
            break;
        case UInt32:
        case Int32:
            ((int[]) dst)[(int) dstoffset] = ((int[]) src)[(int) srcoffset];
            break;
        case UInt64:
        case Int64:
            ((long[]) dst)[(int) dstoffset] = ((long[]) src)[(int) srcoffset];
            break;
        case Float32:
            ((float[]) dst)[(int) dstoffset] = ((float[]) src)[(int) srcoffset];
            break;
        case Float64:
            ((double[]) dst)[(int) dstoffset] = ((double[]) src)[(int) srcoffset];
            break;
        case Opaque:
            // Sigh, bytebuffer hidden by CDM
            Object o = ((Object[]) src)[(int) srcoffset];
            ((ByteBuffer[]) dst)[(int) dstoffset] = (ByteBuffer) o;
            break;
        case String:
            // Sigh, String hidden by CDM
            o = ((Object[]) src)[(int) srcoffset];
            ((String[]) dst)[(int) dstoffset] = (String) o;
            break;
        case Enum:
            vectorcopy(((DapEnumeration) datatype).getBaseType(), src, dst, srcoffset, dstoffset);
            break;
        case Structure:
        case Sequence:
        default:
            throw new DapException("Attempt to read non-atomic value of type: " + datatype);
        }
    }

    /**
     * convert a string to a specified cdmtype
     * Note that if en is defined, then we attempt
     * to convert the string as enum const
     *
     * @param cdmtype
     * @param en
     * @param o
     * @return
     */
    static public Object
    attributeParse(DataType cdmtype, EnumTypedef en, Object o)
    {
        String so = o.toString();
        if(en != null) {
            switch (cdmtype) {
            case ENUM1:
            case ENUM2:
            case ENUM4:
                if(!(o instanceof Integer))
                    throw new ConversionException(o.toString());
                int eval = (Integer) o;
                String econst = en.lookupEnumString(eval);
                if(econst == null)
                    throw new ConversionException(o.toString());
                return econst;
            default:
                throw new ConversionException(o.toString());
            }
        }
        long lval = 0;
        double dval = 0.0;
        boolean islong = true;
        boolean isdouble = true;
        // Do a quick conversion checks
        try {
            lval = Long.parseLong(so);
        } catch (NumberFormatException nfe) {
            islong = false;
        }
        try {
            dval = Double.parseDouble(so);
        } catch (NumberFormatException nfe) {
            isdouble = false;
        }
        o = null; // default is not convertible
        switch (cdmtype) {
        case BOOLEAN:
            if(so.equalsIgnoreCase("false")
                    || (islong && lval == 0))
                o = Boolean.FALSE;
            else
                o = Boolean.TRUE;
            break;
        case BYTE:
            if(islong) o = Byte.valueOf((byte) lval);
            break;
        case SHORT:
            if(islong) o = Short.valueOf((short) lval);
            break;
        case INT:
            if(islong) o = Integer.valueOf((int) lval);
            break;
        case LONG:
            if(islong) o = Long.valueOf(lval);
            break;
        case UBYTE:  // Keep the proper bit pattern
            if(islong) o = Byte.valueOf((byte) (lval & 0xFFL));
            break;
        case USHORT:
            if(islong) o = Short.valueOf((short) (lval & 0xFFFFL));
            break;
        case UINT:
            if(islong) o = Integer.valueOf((int) (lval & 0xFFFFFFFFL));
            break;
        case ULONG:  //Need to resort to BigInteger
            BigInteger bi = new BigInteger(so);
            bi = bi.and(LONGMASK);
            o = (Long) bi.longValue();
            break;
        case FLOAT:
            if(islong && !isdouble) {
                dval = (double) lval;
                isdouble = true;
            }
            if(isdouble) o = (Float) ((float) dval);
            break;
        case DOUBLE:
            if(islong && !isdouble) {
                dval = (double) lval;
                isdouble = true;
            }
            if(isdouble) o = (Double) (dval);
            break;
        case STRING:
            return so;
        case OPAQUE:  // Big Integer then ByteBuffer
            if(so.startsWith("0x") || so.startsWith("0X"))
                so = so.substring(2);
            bi = new BigInteger(so, 16);
            // Now extract bytes
            byte[] bb = bi.toByteArray();
            o = ByteBuffer.wrap(bb);
            break;
        default:
            throw new ConversionException(o.toString());
        }
        if(o == null)
            throw new ConversionException(o.toString());
        return o;
    }

    static public boolean
    isPrimitiveVector(DataType type, Object o)
    {
        Class c = o.getClass();
        if(!c.isArray())
            return false;
        // cannot use isAssignableFrom, I think because primitive
        switch (type) {
        case BOOLEAN:
            return o instanceof boolean[];
        case CHAR:
            return o instanceof char[];
        case ENUM1:
        case BYTE:
        case UBYTE:
            return o instanceof byte[];
        case ENUM2:
        case SHORT:
        case USHORT:
            return o instanceof short[];
        case ENUM4:
        case INT:
        case UINT:
            return o instanceof int[];
        case LONG:
        case ULONG:
            return o instanceof long[];
        case FLOAT:
            return o instanceof float[];
        case DOUBLE:
            return o instanceof double[];
        case STRING:
            return o instanceof String[];
        case OPAQUE:
            return o instanceof ByteBuffer[];
        default:
            break;
        }
        return false;
    }

    static public Array
    arrayify(DataType datatype, Object o)
    {
        // 1. o is a constant
        if(!o.getClass().isArray()) {
            Object ovec = createVector(datatype, 1);
            java.lang.reflect.Array.set(ovec, 0, o);
            o = ovec;
        }
        int[] shape = new int[]{java.lang.reflect.Array.getLength(o)};
        return Array.factory(datatype, shape, o);
    }

    static public Array
    arrayify(DapType type, Object o)
    {
        if(type.getAtomicType() == TypeSort.Enum)
            return arrayify(((DapEnumeration) type).getBaseType(), o);
        return arrayify(CDMTypeFcns.daptype2cdmtype(type), o);
    }

    static public List
    listify(Object vector)
    {
        List list = new ArrayList();
        int icount = java.lang.reflect.Array.getLength(vector);
        for(int i = 0; i < icount; i++) {
            list.add(java.lang.reflect.Array.get(vector, i));
        }
        return list;
    }

    /*
    static public Array
    arraysection(Array a, List<Slice> slices)
    {
	int rank = slices.size();
	int[] origin = new int[rank];
	int[] subshape = new int[rank];
	for(int i = 0; i < rank; i++) {
	    origin[i] = 0;
	    subshape[i] = (int) index.get(i);
	}
	subshape[rank - 1] = 1; // remove  vlen dimension
	    Array records;
	    try {
		records = seqarray.section(origin, subshape, null);
	    } catch (InvalidRangeException e) {
		throw new DapException("Illegal index", e);
	    }
	    */
}


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

import javax.annotation.concurrent.Immutable;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Provides support for unknown base units. This can be used, for example, to
 * accomodate an unknown unit (e.g. "foo"). Values in such a unit will only be
 * convertible with units derived from "foo" (e.g. "20 foo").
 *
 * @author Steven R. Emmerson
 */
@Immutable
public final class UnknownUnit extends BaseUnit {
  private static final long serialVersionUID = 1L;
  /**
   * The name-to-unit map.
   *
   * @serial
   */
  private static final SortedMap<String, UnknownUnit> map = new TreeMap<>();

  /**
   * Constructs from a name.
   *
   * @param name The name of the unit.
   */
  private UnknownUnit(final String name) throws NameException {
    super(UnitName.newUnitName(name, null, name), BaseQuantity.UNKNOWN);
  }

  /**
   * Factory method for constructing an unknown unit from a name.
   *
   * @param name The name of the unit.
   * @return The unknown unit.
   * @throws NameException name == null.
   */
  public static UnknownUnit create(String name) throws NameException {
    UnknownUnit unit;
    name = name.toLowerCase();
    synchronized (map) {
      unit = map.get(name);
      if (unit == null) {
        unit = new UnknownUnit(name);
        map.put(unit.getName(), unit);
        map.put(unit.getPlural(), unit);
      }
    }
    return unit;
  }

    /*
     * From Unit:
     */

  /**
   * Indicates if this unit is semantically identical to an object.
   *
   * @param object The object.
   * @return true if and only if this instance is semantically
   * identical to the object.
   */
  @Override
  public boolean equals(final Object object) {
    if (this == object) {
      return true;
    }
    if (!(object instanceof UnknownUnit)) {
      return false;
    }
    final UnknownUnit that = (UnknownUnit) object;
    return getName().equalsIgnoreCase(that.getName());
  }

  /**
   * Returns the hash code of this instance.
   *
   * @return The hash code of this instance.
   */
  @Override
  public int hashCode() {
    return getName().toLowerCase().hashCode();
  }

  /**
   * Indicates if this unit is dimensionless. An unknown unit is never
   * dimensionless.
   *
   * @return false always.
   */
  @Override
  public boolean isDimensionless() {
    return false;
  }

}


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

package ucar.httpservices;

import java.io.Closeable;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * The goal of this class is to allow
 * other classes to access the data stream
 * associated with a method response.
 * It tracks the method and the session
 * to allow them to be closed
 * when the stream hits eof.
 * It also guarantees that any remaining unconsumed
 * input is consumed.
 *
 * Note that this class is not public in the package.
 *
 * Note that this code now includes the equivalent of
 * Tom Kunicki's proposed pull request, but with the HTTPMethod
 * close extension he proposes but does not implement.
 * Pull request: https://github.com/tkunicki-usgs/thredds/commit/3b750ec0016a137db66336adeac421a9202b9d30
 * Is this class needed in httpclient 4.5+ any more?
 *
 */

public class HTTPMethodStream extends FilterInputStream implements Closeable
{
    //////////////////////////////////////////////////////////////////////////
    static public org.slf4j.Logger log = HTTPSession.log;

    //////////////////////////////////////////////////////////////////////////
    HTTPMethod method = null;
    InputStream stream = null; // in case someone wants to retrieve it
    boolean closed = false;

    HTTPMethodStream(InputStream stream, HTTPMethod method)
    {
	super(stream);
	this.method = method;
	this.stream = stream;
    }

    boolean getClosed() {return closed;}

    InputStream getStream() {return stream;}

    /**
       * Closes this input stream and releases any system resources associated
       * with the stream; closes the method also.
       *
       * @exception  IOException  if an I/O error occurs; but not if close
       *                          is called twice.
       */
    @Override
    public void close() throws IOException
    {
        if(closed)
            return; /* Allow multiple close calls */
        closed = true;
        try {
            consume();
        } finally {
            super.close();
        }
        if(method != null) method.close();
    }

    void consume()
    {
        try {
            long consumed = 0;
            long available;
            while ((available = available()) > 0) {
            consumed += skip(available);
            }
            if (consumed > 0) {
                log.debug("HTTPMethodStream: unconsumed data");
            }
        } catch (IOException ioe) {/*ignore*/};
      }

    public boolean isClosed() {return closed;}
}


/*
 * 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 java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;

import ucar.ma2.StructureData;
import ucar.ma2.StructureDataFactory;
import ucar.ma2.StructureDataIterator;
import ucar.ma2.StructureDataIteratorLimited;
import ucar.ma2.StructureMembers;
import ucar.nc2.Variable;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.ft.FeatureDatasetFactoryManager;
import ucar.nc2.ft.point.StationFeature;
import ucar.nc2.ft.point.StationFeatureImpl;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateFormatter;
import ucar.nc2.time.CalendarDateUnit;
import ucar.unidata.geoloc.EarthLocation;
import ucar.unidata.geoloc.EarthLocationImpl;

/**
 * Implements "nested table" views of point feature datasets.
 * A NestedTable is initialized with a TableConfig.
 * A NestedTable Table is created after the Tables have been joined, and the leaves identified.
 * It is a single chain of Table objects from child to parent. Highest parent is root. Lowest child is leaf
 *

 * A nested table starts with a leaf table (no children), plus all of its parents.
 * There is a "join" for each child and parent.
 *

 * Assumes that we have Tables that can be iterated over with a StructureDataIterator.
 * A parent-child join assumes that for each row of the parent, a StructureDataIterator exists that
 * iterates over the rows of the child table for that parent.
 *

 * Nested Tables must be put in canonical form, based on feature type:
 *

 *
 point : obsTable
 *
 station : stnTable -> obsTable
 *
 traj : trajTable -> obsTable
 *
 profile : profileTable -> obsTable
 *
 stationProfile : stnTable -> profileTable -> obsTable
 *
 section : sectionTable -> trajTable -> obsTable
 *

 *
 * @author caron
 * @since Mar 28, 2008
 */
public class NestedTable {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NestedTable.class);

  private NetcdfDataset ds;
  private Formatter errlog;
  private Table leaf, root;
  private FeatureType featureType;

  private CoordVarExtractor timeVE, nomTimeVE, latVE, lonVE, altVE;
  private CoordVarExtractor stnVE, stnDescVE, wmoVE, stnAltVE, idVE, missingVE;
  private List<Variable> extras;

  private int nlevels;

  NestedTable(NetcdfDataset ds, TableConfig config, Formatter errlog) {
    this.ds = ds;
    this.errlog = errlog;

    this.leaf = Table.factory(ds, config);
    this.root = getRoot();

    // use the featureType from the highest level table
    nlevels = 0;
    Table t = leaf;
    while (t != null) {
      if (t.getFeatureType() != null) featureType = t.getFeatureType();
      t = t.parent;
      // if (!(t instanceof Table.TableTop)) // LOOK using nlevels is fishy
      nlevels++;
    }
    if (featureType == null)
      featureType = FeatureDatasetFactoryManager.findFeatureType(ds);

    /* find joins with extra variables
    t = leaf;
    while (t != null) {
      if (t.extraJoins != null) {
        for (Join j : t.extraJoins) {
          addExtraVariable(j.getExtraVariable());
        }
      }
      t = t.parent; // recurse upwards
    } */

    // will find the first one, starting at the leaf and going up
    timeVE = findCoordinateAxis(Table.CoordName.Time, leaf, 0);
    latVE = findCoordinateAxis(Table.CoordName.Lat, leaf, 0);
    lonVE = findCoordinateAxis(Table.CoordName.Lon, leaf, 0);
    altVE = findCoordinateAxis(Table.CoordName.Elev, leaf, 0);
    nomTimeVE = findCoordinateAxis(Table.CoordName.TimeNominal, leaf, 0);

    // search for station info
    stnVE = findCoordinateAxis(Table.CoordName.StnId, leaf, 0);
    stnDescVE = findCoordinateAxis(Table.CoordName.StnDesc, leaf, 0);
    wmoVE = findCoordinateAxis(Table.CoordName.WmoId, leaf, 0);
    stnAltVE = findCoordinateAxis(Table.CoordName.StnAlt, leaf, 0);

    missingVE = findCoordinateAxis(Table.CoordName.MissingVar, leaf, 0);
    idVE = findCoordinateAxis(Table.CoordName.FeatureId, root, nlevels - 1); // LOOK start at root ??

    // LOOK: Major kludge
    if (featureType == null) {
      if (nlevels == 1) featureType = FeatureType.POINT;
      if (nlevels == 2) featureType = FeatureType.STATION;
      if (nlevels == 3) featureType = FeatureType.STATION_PROFILE;
    }

    // find coordinates that are not part of the extras
    for (CoordinateAxis axis : ds.getCoordinateAxes()) {
      if (!isCoordinate(axis) && !isExtra(axis)
              && axis.getDimensionsAll().size() <= 1)  // Only permit 0-D and 1-D axes as extra variables.
        addExtraVariable(axis);
    }

    /* check for singleton
    if (((nlevels == 1) && (featureType == FeatureType.STATION) || (featureType == FeatureType.PROFILE) || (featureType == FeatureType.TRAJECTORY)) ||
            ((nlevels == 2) && (featureType == FeatureType.STATION_PROFILE) || (featureType == FeatureType.TRAJECTORY_PROFILE))) {

      // singleton. use file name as feature name, so aggregation will work
      StructureData sdata = StructureDataFactory.make(featureVariableName, ds.getLocation());
      TableConfig parentConfig = new TableConfig(Table.Type.Singleton, featureType.toString());
      parentConfig.sdata = sdata;
      root = Table.factory(ds, parentConfig);

      nlevels++;
    } // */
  }

  Table getRoot() {
    Table p = leaf;
    while (p.parent != null) p = p.parent;
    return p;
  }

  Table getLeaf() {
    return leaf;
  }

  List<Variable> getExtras() {
    return extras;
  }

  private void addExtraVariable(Variable v) {
    if (v == null) return;
    if (extras == null) extras = new ArrayList<>();
    extras.add(v);
  }

  // Has v already been added to the set of extra variables?
  private boolean isExtra(Variable v) {
    return v != null && extras != null && extras.contains(v);
  }

  // Is v a coordinate axis for this feature type?
  private boolean isCoordinate(Variable v) {
    if (v == null) return false;
    String name = v.getShortName();
    return (latVE != null && latVE.axisName.equals(name)) ||
            (lonVE != null && lonVE.axisName.equals(name)) ||
            (altVE != null && altVE.axisName.equals(name)) ||
            (stnAltVE != null && stnAltVE.axisName.equals(name)) ||
            (timeVE != null && timeVE.axisName.equals(name)) ||
            (nomTimeVE != null && nomTimeVE.axisName.equals(name));
  }

  // find a coord axis of the given type in the table and its parents
  private CoordVarExtractor findCoordinateAxis(Table.CoordName coordName, Table t, int nestingLevel) {
    if (t == null) return null;

    String axisName = t.findCoordinateVariableName(coordName);

    if (axisName != null) {
      VariableDS v = t.findVariable(axisName);
      if (v != null)
        return new CoordVarExtractorVariable(v, axisName, nestingLevel);

      if (t.extraJoins != null) {
        for (Join j : t.extraJoins) {
          v = j.findVariable(axisName);
          if (v != null)
            return new CoordVarExtractorVariable(v, axisName, nestingLevel);
        }
      }

      // see if its in the StructureData
      if (t instanceof Table.TableSingleton) {
        Table.TableSingleton ts = (Table.TableSingleton) t;
        return new CoordVarStructureData(axisName, ts.sdata);
      }

      // see if its at the top level
      if (t instanceof Table.TableTop) {
        v = (VariableDS) ds.findVariable(axisName);

        if (v != null)
          return new CoordVarTop(v);
        else
          return new CoordVarConstant(coordName.toString(), "", axisName); // assume its the actual value
      }

      errlog.format("NestedTable: cant find variable '%s' for coordinate type %s %n", axisName, coordName);
    }

    // check the parent
    return findCoordinateAxis(coordName, t.parent, nestingLevel + 1);
  }

  /////////////////////////////////////////////////////////////////////////
  // knows how to get specific coordinate data from a table or its parents
  private static class CoordVarExtractorVariable extends CoordVarExtractor {
    protected VariableDS coordVar;

    CoordVarExtractorVariable(VariableDS v, String axisName, int nestingLevel) {
      super(axisName, nestingLevel);
      this.coordVar = v;
    }

    @Override
    public String getCoordValueString(StructureData sdata) {
      if (coordVar.getDataType().isString())
        return sdata.getScalarString(memberName);
      else if (coordVar.getDataType().isIntegral())
        return Integer.toString(sdata.convertScalarInt(memberName));
      else
        return Double.toString(sdata.convertScalarDouble(memberName));
    }

    @Override
    public String getUnitsString() {
      return coordVar.getUnitsString();
    }

    @Override
    public boolean isString() {
      return coordVar.getDataType().isString();
    }

    @Override
    public boolean isMissing(StructureData sdata) {
      if (isString()) {
        String s = getCoordValueString(sdata);
        double test = (s.length() == 0) ? 0 : (double) s.charAt(0);
        return coordVar.isMissing(test);
      } else {
        double val = getCoordValue(sdata);
        return coordVar.isMissing(val);
      }
    }

    @Override
    public double getCoordValue(StructureData sdata) {
      return sdata.convertScalarDouble(memberName);
    }

    @Override
    public boolean isInt() {
      return coordVar.getDataType().isIntegral();
    }

    @Override
    public long getCoordValueLong(StructureData sdata) {
      return sdata.convertScalarLong(memberName);
    }
  }

  /////////////////////////////////////////////////////////////////////////
  // knows how to get specific coordinate data from a table or its parents
  private static class CoordVarTop extends CoordVarExtractor {
    protected VariableDS varTop;

    CoordVarTop(VariableDS v) {
      super(v.getFullName(), 0);
      this.varTop = v;
    }

    @Override
    public double getCoordValue(StructureData sdata) {
      try {
        return varTop.readScalarDouble();
      } catch (IOException e) {
        throw new RuntimeException(e.getMessage());
      }
    }

    @Override
    public String getUnitsString() {
      return varTop.getUnitsString();
    }

    @Override
    public boolean isString() {
      return varTop.getDataType().isString();
    }

    @Override
    public boolean isInt() {
      return varTop.getDataType().isIntegral();
    }

    @Override
    public long getCoordValueLong(StructureData sdata) {
      try {
        return varTop.readScalarLong();
      } catch (IOException e) {
        throw new RuntimeException(e.getMessage());
      }
    }

    @Override
    public String getCoordValueString(StructureData sdata) {
      try {
        return varTop.readScalarString();
      } catch (IOException e) {
        throw new RuntimeException(e.getMessage());
      }
    }

    @Override
    public boolean isMissing(StructureData sdata) {
      if (isString()) return false;
      double val = getCoordValue(sdata);
      return varTop.isMissing(val);
    }
  }

  /////////////////////////////////////////////////////////////////////////
  // knows how to get specific coordinate data from a table or its parents
  private static class CoordVarStructureData extends CoordVarExtractor {
    protected StructureData sdata;

    CoordVarStructureData(String axisName, StructureData sdata) {
      super(axisName, 0);
      this.sdata = sdata;
    }

    @Override
    public double getCoordValue(StructureData ignore) {
      return sdata.convertScalarDouble(memberName);
    }

    @Override
    public String getCoordValueString(StructureData ignore) {
      return sdata.getScalarString(memberName);
    }

    @Override
    public String getUnitsString() {
      StructureMembers.Member m = sdata.findMember(memberName);
      return m.getUnitsString();
    }

    @Override
    public boolean isString() {
      StructureMembers.Member m = sdata.findMember(memberName);
      return m.getDataType().isString();
    }

    @Override
    public boolean isInt() {
      StructureMembers.Member m = sdata.findMember(memberName);
      return m.getDataType().isIntegral();
    }

    @Override
    public long getCoordValueLong(StructureData sdata) {
      return sdata.convertScalarLong(memberName);
    }

    @Override
    public boolean isMissing(StructureData sdata) {
      return false;
    }

  }

  /////////////////////////////////////////////////////////////////////////
  // a constant coordinate variable
  private static class CoordVarConstant extends CoordVarExtractor {
    String units, value;

    CoordVarConstant(String name, String units, String value) {
      super(name, 0);
      this.units = units;
      this.value = value;
    }

    @Override
    public double getCoordValue(StructureData sdata) {
      return Double.parseDouble(value);
    }

    @Override
    public long getCoordValueLong(StructureData sdata) {
      return Long.parseLong(value);
    }

    @Override
    public String getCoordValueString(StructureData sdata) {
      return value;
    }

    @Override
    public String getUnitsString() {
      return units;
    }

    @Override
    public boolean isString() {
      return true;
    }

    @Override
    public boolean isInt() {
      return false;
    }

    @Override
    public boolean isMissing(StructureData sdata) {
      return false;
    }

    @Override
    public String toString() {
      return "CoordVarConstant value= " + value;
    }
  }

  public FeatureType getFeatureType() {
    return featureType;
  }

  public int getNumberOfLevels() {
    return nlevels;
  }

  public boolean hasCoords() {
    return (timeVE != null) && (latVE != null) && (lonVE != null);
  }

  public CalendarDateUnit getTimeUnit() {
    try {
      return CalendarDateUnit.of(null, timeVE.getUnitsString()); // LOOK dont know the calendar
    } catch (Exception e) {
      throw new IllegalArgumentException("Error on time string = " + timeVE.getUnitsString() + " == " + e.getMessage());
    }
  }

  public String getAltUnits() {
    if (altVE != null) return altVE.getUnitsString();         // fishy
    if (stnAltVE != null) return stnAltVE.getUnitsString();
    return null;
  }

  public List<VariableSimpleIF> getDataVariables() {
    List<VariableSimpleIF> data = new ArrayList<>();
    addDataVariables(data, leaf);
    Collections.sort(data);
    return data;
  }

  // use recursion so that parent variables come first
  private void addDataVariables(List<VariableSimpleIF> list, Table t) {
    if (t.parent != null) addDataVariables(list, t.parent);
    for (VariableSimpleIF col : t.cols.values()) {
      if (t.nondataVars.contains(col.getFullName())) continue;
      if (t.nondataVars.contains(col.getShortName())) continue;  // fishy
      list.add(col);
    }
  }

  public String getName() {
    Formatter formatter = new Formatter();
    formatter.format("%s", root.getName());

    Table t = root;
    while (t.child != null) {
      t = t.child;
      String name = t.getName() != null ? t.getName() : "anon";
      formatter.format("/%s", name);
    }
    return formatter.toString();
  }

  public String toString() {
    Formatter formatter = new Formatter();
    formatter.format("NestedTable = %s%n", getName());
    formatter.format("  Time= %s%n", timeVE);
    formatter.format("  Lat= %s%n", latVE);
    formatter.format("  Lon= %s%n", lonVE);
    formatter.format("  Height= %s%n", altVE);

    return formatter.toString();
  }

  public void show(Formatter formatter) {
    formatter.format(" NestedTable = %s%n", getName());
    formatter.format("   nlevels = %d%n", nlevels);
    leaf.show(formatter, 2);
  }

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

  public double getObsTime(Cursor cursor) {
    return getTime(timeVE, cursor.tableData);
  }

  public double getNomTime(Cursor cursor) {
    return getTime(nomTimeVE, cursor.tableData);
  }

  private double getTime(CoordVarExtractor cve, StructureData[] tableData) {
    if (cve == null) return Double.NaN;
    if (tableData[cve.nestingLevel] == null) return Double.NaN;

    if (cve.isString()) {
      String timeString = timeVE.getCoordValueString(tableData);
      CalendarDate date = CalendarDateFormatter.isoStringToCalendarDate(null, timeString);
      if (date == null) {
        log.error("Cant parse date - not ISO formatted, = " + timeString);
        return 0.0;
      }
      return date.getMillis();

    } else {
      return cve.getCoordValue(tableData);
    }
  }

  public double getLatitude(Cursor cursor) {
    return latVE.getCoordValue(cursor.tableData);
  }

  public double getLongitude(Cursor cursor) {
    return lonVE.getCoordValue(cursor.tableData);
  }

  public EarthLocation getEarthLocation(Cursor cursor) {
    double lat = latVE.getCoordValue(cursor.tableData);
    double lon = lonVE.getCoordValue(cursor.tableData);
    double alt = (altVE == null) ? Double.NaN : altVE.getCoordValue(cursor.tableData);
    if (stnAltVE != null) {
      double stnElev = stnAltVE.getCoordValue(cursor.tableData);
      if (altVE == null)
        alt = stnElev;
      else
        alt += stnElev;
    }
    return new EarthLocationImpl(lat, lon, alt);
  }

  public String getFeatureName(Cursor cursor) {
    int count = 0;
    Table t = leaf;
    while (count++ < cursor.currentIndex)
      t = t.parent;

    if (t.feature_id == null) return "unknown";
    StructureData sdata = cursor.getParentStructure();
    if (sdata == null) return "unknown";
    StructureMembers.Member m = sdata.findMember(t.feature_id);
    if (m == null) return "unknown";

    if (m.getDataType().isString())
      return sdata.getScalarString(m);
    else if (m.getDataType().isIntegral())
      return Integer.toString(sdata.convertScalarInt(m));
    else
      return Double.toString(sdata.convertScalarDouble(m));
  }

  public boolean isFeatureMissing(StructureData sdata) {
    return idVE != null && idVE.isMissing(sdata);
  }

  public boolean isTimeMissing(Cursor cursor) {
    return timeVE != null && timeVE.isMissing(cursor.tableData);
  }

  public boolean isAltMissing(Cursor cursor) {
    return altVE != null && altVE.isMissing(cursor.tableData);
  }

  public boolean isMissing(Cursor cursor) {
    return missingVE != null && missingVE.isMissing(cursor.tableData);
  }

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

  public StructureData makeObsStructureData(Cursor cursor) {
    return StructureDataFactory.make(cursor.tableData);
  }

  public StructureData makeObsStructureData(Cursor cursor, int nest) {
    return cursor.tableData[nest];
  }

  /* public void addParentJoin(Cursor cursor) throws IOException {
    Table t = leaf;
    int level = 0;
    while (t != null) {
      addParentJoin(t, level, cursor);
      level++;
      t = t.parent;
    }
  }  */

  // add table join to this cursor level
  void addParentJoin(Cursor cursor) throws IOException {
    int level = cursor.currentIndex;
    Table t = getTable(level);
    if (t.extraJoins != null) {
      List<StructureData> sdata = new ArrayList<>(3);
      sdata.add(cursor.tableData[level]);
      for (Join j : t.extraJoins) {
        sdata.add(j.getJoinData(cursor));
      }
      cursor.tableData[level] = StructureDataFactory.make(sdata.toArray(new StructureData[sdata.size()]));  // LOOK should try to consolidate
    }
  }

  private Table getTable(int level) {
    Table t = leaf;
    int count = 0;
    while (count < level) {
      count++;
      t = t.parent;
    }
    return t;
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  // not clear these methods should be here

  //// Point

  public StructureDataIterator getObsDataIterator(Cursor cursor) throws IOException {
    return root.getStructureDataIterator(cursor);
  }

  //// Station or Station_Profile
  public StructureDataIterator getStationDataIterator() throws IOException {
    Table stationTable = root;
    StructureDataIterator siter = stationTable.getStructureDataIterator(null);

    if (stationTable.limit != null) {
      Variable limitV = ds.findVariable(stationTable.limit);
      int limit = limitV.readScalarInt();
      return new StructureDataIteratorLimited(siter, limit);
    }

    return siter;
  }

  //// Trajectory, Profile, Section
  public StructureDataIterator getRootFeatureDataIterator() throws IOException {
    return root.getStructureDataIterator(null);
  }

  public StructureDataIterator getLeafFeatureDataIterator(Cursor cursor) throws IOException {
    return leaf.getStructureDataIterator(cursor);
  }

  public StructureDataIterator getMiddleFeatureDataIterator(Cursor cursor) throws IOException {
    return leaf.parent.getStructureDataIterator(cursor);  // the middle table
  }

  // also called from StandardPointFeatureIterator
  StationFeature makeStation(StructureData stationData) {
    if (stnVE.isMissing(stationData)) return null;
    String stationName = stnVE.getCoordValueAsString(stationData);

    String stationDesc = (stnDescVE == null) ? "" : stnDescVE.getCoordValueAsString(stationData);
    String stnWmoId = (wmoVE == null) ? "" : wmoVE.getCoordValueAsString(stationData);

    double lat = latVE.getCoordValue(stationData);
    double lon = lonVE.getCoordValue(stationData);
    double elev = (stnAltVE == null) ? Double.NaN : stnAltVE.getCoordValue(stationData);

    // missing lat, lon means skip this station
    if (Double.isNaN(lat) || Double.isNaN(lon)) return null;

    return new StationFeatureImpl(stationName, stationDesc, stnWmoId, lat, lon, elev, -1, stationData);
  }

  /////////////////////////////////////////////////////////
  // Table.Construct: stations get constructed by reading the obs and extracting

  /* private List<Station> constructStations(Table.TableConstruct stationTable) throws IOException {
    Map<String, StationConstruct> stnMap = new HashMap<String, StationConstruct>();
    ArrayList<Station> result = new ArrayList<Station>();
    StructureDataIterator iter = stationTable.getStructureDataIterator(null, -1); // this will be the obs structure
    int recno = 0;
    while (iter.hasNext()) {
      StructureData sdata = iter.next();
      String stationName = stnVE.getCoordValueString(sdata);
      StationConstruct s = stnMap.get(stationName);
      if (s == null) {
        s = makeStationConstruct(stationName, stationTable.getObsStructure(), sdata);
        stnMap.put(stationName, s);
        result.add(s);
      }
      double obsTime = timeVE.getCoordValue(sdata);
      s.addIndex(recno, obsTime);
      recno++;
    }

    return result;
  }

  private StationConstruct makeStationConstruct(String stationName, Structure obsStruct, StructureData stationData) {
    String stationDesc = (stnDescVE == null) ? "" : stnDescVE.getCoordValueString(stationData);
    String stnWmoId = (wmoVE == null) ? "" : wmoVE.getCoordValueString(stationData);

    double lat = latVE.getCoordValue(stationData);
    double lon = lonVE.getCoordValue(stationData);
    double elev = (stnAltVE == null) ? Double.NaN : stnAltVE.getCoordValue(stationData);

    return new StationConstruct(stationName, obsStruct, stationDesc, stnWmoId, lat, lon, elev);
  }

  private class StationConstruct extends StationImpl {
    List<Index> index;
    Structure obsStruct;

    StationConstruct(String name, Structure obsStruct, String desc, String wmoId, double lat, double lon, double alt) {
      super(name, desc, wmoId, lat, lon, alt);
      this.obsStruct = obsStruct;
    }

    void addIndex(int recno, double time) {
      if (index == null)
        index = new ArrayList<Index>();

      Index i = new Index();
      i.recno = recno;
      i.time = time;
      index.add(i);
    }

    class Index {
      int recno;
      double time;
    }

    StructureDataIterator getStructureDataIterator(int bufferSize) {
      return new IndexedStructureDataIterator();
    }

    private class IndexedStructureDataIterator implements ucar.ma2.StructureDataIterator {
      private int count = 0;

      public boolean hasNext() throws IOException {
        return count < index.size();
      }

      public StructureData next() throws IOException {
        Index i = index.get(count++);
        try {
          return obsStruct.readStructure(i.recno);
        } catch (InvalidRangeException e) {
          throw new IllegalStateException("bad recnum " + i.recno, e);
        }
      }

      public void setBufferSize(int bytes) {
      }

      public StructureDataIterator reset() {
        count = 0;
        return this;
      }
    }

  } */

}


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

package dap4.cdm.nc2;

import dap4.cdm.CDMTypeFcns;
import dap4.cdm.CDMUtil;
import dap4.cdm.NodeMap;
import dap4.core.data.DSP;
import dap4.core.dmr.*;
import dap4.core.util.Convert;
import dap4.core.util.CoreTypeFcns;
import dap4.core.util.DapException;
import dap4.core.util.DapSort;
import dap4.dap4lib.LibTypeFcns;
import ucar.ma2.DataType;
import ucar.ma2.ForbiddenConversionException;
import ucar.nc2.*;
import ucar.nc2.stream.NcStreamProto;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Convert a DSP to corresponding NetcdfFile CDM metadata.
 * Note that we have a problem with <map>.
 */
public class DMRToCDM
{
    //////////////////////////////////////////////////
    // Instance Variables

    protected DapNetcdfFile ncfile;
    protected DSP dsp;
    protected DapDataset dmr;

    protected NodeMap<CDMNode,DapNode> nodemap;

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

    /**
     * @param ncfile - the NetcdfFile object
     * @param dsp    - the DSP being wrapped
     */

    public DMRToCDM(DapNetcdfFile ncfile, DSP dsp)
            throws DapException
    {
        this.ncfile = ncfile;
        this.dsp = dsp;
        this.dmr = dsp.getDMR();
        this.nodemap = new NodeMap<>();
    }

    //////////////////////////////////////////////////
    // API

    /**
     * Do the conversion and return a NodeMap
     * representing the conversion.
     *
     * @throws DapException
     */

    public NodeMap<CDMNode,DapNode>
    create()
            throws DapException
    {
        // Netcdf Dataset will already have a root group
        Group cdmroot = ncfile.getRootGroup();
        this.nodemap.put(cdmroot,this.dmr);
        fillGroup(cdmroot, this.dmr, ncfile);
        return this.nodemap;
    }

    protected void
    fillGroup(Group cdmparent, DapGroup dapparent, NetcdfFile ncfile)
            throws DapException
    {
        // Create dimensions in this group
        for(DapDimension dim : dapparent.getDimensions()) {
            if(!dim.isShared())
                continue; // anonymous
            createDimension(dim, cdmparent);
        }

        // Create enums in this group
        for(DapEnumeration dapenum : dapparent.getEnums()) {
            DapType basetype = dapenum.getBaseType();
            DataType cdmbasetype = CDMTypeFcns.enumTypeFor(basetype);
            Map<Integer, String> map = new HashMap<Integer, String>();
            List<String> ecnames = dapenum.getNames();
            for(String ecname : ecnames) {
                DapEnumConst value = dapenum.lookup(ecname);
                map.put(value.getIntValue(), ecname);
            }

            EnumTypedef cdmenum = new EnumTypedef(dapenum.getShortName(),
                    map, cdmbasetype);
            this.nodemap.put(cdmenum,dapenum);
            cdmparent.addEnumeration(cdmenum);
        }

        // Create variables
        for(DapVariable var : dapparent.getVariables()) {
            createVar(var, ncfile, cdmparent, null);
        }

        // Create subgroups
        for(DapGroup subgroup : dapparent.getGroups()) {
            createGroup(subgroup, cdmparent, ncfile);
        }

        // Create group level attributes
        for(Map.Entry<String, DapAttribute> entry : dapparent.getAttributes().entrySet()) {
            DapAttribute attr = entry.getValue();
            Attribute cdmattr = createAttribute(attr);
            cdmparent.addAttribute(cdmattr);
        }
    }

    protected void
    createGroup(DapGroup dapgroup, Group cdmparent, NetcdfFile ncfile)
            throws DapException
    {
        Group cdmgroup = new Group(ncfile, cdmparent, dapgroup.getShortName());
        this.nodemap.put(cdmgroup,dapgroup);
        fillGroup(cdmgroup, dapgroup, ncfile);
        if(cdmgroup != null)
            cdmparent.addGroup(cdmgroup);
    }

    /**
     * Create a variable or field
     *
     * @param dapvar          the template variable
     * @param ncfile          the containing NetcdfFile (really NetcdfDataset)
     * @param cdmgroup        the containing CDM group
     * @param cdmparentstruct the containing CDM structure (or null)
     */
    protected void
    createVar(DapVariable dapvar, NetcdfFile ncfile,
              Group cdmgroup, Structure cdmparentstruct)
            throws DapException
    {
        Variable cdmvar = null;
	    DapType basetype = dapvar.getBaseType();
        if(basetype.isAtomic()) {
            DapVariable atomvar = (DapVariable) dapvar;
            cdmvar = new Variable(ncfile,
                    cdmgroup,
                    cdmparentstruct,
                    atomvar.getShortName());
            DataType cdmbasetype;
            if(basetype.isEnumType())
                cdmbasetype = CDMTypeFcns.enumTypeFor(basetype);
            else
                cdmbasetype = CDMTypeFcns.daptype2cdmtype(basetype);
            if(cdmbasetype == null)
                throw new DapException("Unknown basetype:" + basetype);
            cdmvar.setDataType(cdmbasetype);
            if(basetype.isEnumType()) {
                EnumTypedef cdmenum = (EnumTypedef) this.nodemap.get(basetype);
                if(cdmenum == null)
                    throw new DapException("Unknown enumeration type:" + basetype.toString());
                cdmvar.setEnumTypedef(cdmenum);
            }
            this.nodemap.put(cdmvar,dapvar);
        } else if(basetype.isStructType()) {
            DapStructure dapstruct = (DapStructure) basetype;
            Structure cdmstruct = new Structure(ncfile,
                    cdmgroup,
                    cdmparentstruct,
                    dapstruct.getShortName());
            cdmvar = cdmstruct;
            this.nodemap.put(cdmvar,dapvar);
            // Add the fields
            for(DapVariable field : dapstruct.getFields()) {
                createVar(field, ncfile, cdmgroup, cdmstruct);
            }
        } else if(basetype.isSeqType()) {
            DapSequence dapseq = (DapSequence) basetype;
            // In general one would convert the sequence
            // to a CDM sequence with vlen
            // so Sequence {...} s[d1]...[dn]
            // => Sequence {...} s[d1]...[dn]
            Sequence cdmseq = new Sequence(ncfile,
                    cdmgroup,
                    cdmparentstruct,
                    dapseq.getShortName());
            cdmvar = cdmseq;
            this.nodemap.put(cdmvar,dapvar);
            // Add the fields
            for(DapVariable field : dapseq.getFields()) {
                createVar(field, ncfile, cdmgroup, cdmseq);
            }
            // If the rank > 0, then add warning attribute
            if(dapvar.getRank() > 0) {
                List value = new ArrayList();
                value.add("CDM does not support Sequences with rank > 0");
                Attribute warning = new Attribute("_WARNING:", value);
                cdmvar.addAttribute(warning);
            }

        } else
            assert (false) : "Unknown variable sort: " + dapvar.getSort();
        int rank = dapvar.getRank();
        List<Dimension> cdmdims = new ArrayList<Dimension>(rank + 1); // +1 for vlen
        for(int i = 0; i < rank; i++) {
            DapDimension dim = dapvar.getDimension(i);
            Dimension cdmdim = createDimensionRef(dim, cdmgroup);
            cdmdims.add(cdmdim);
        }
        if(basetype.isSeqType()) {
            // Add the final vlen
            cdmdims.add(Dimension.VLEN);
        }
        cdmvar.setDimensions(cdmdims);
        // Create variable's attributes
        for(String key : dapvar.getAttributes().keySet()) {
            DapAttribute attr = dapvar.getAttributes().get(key);
            Attribute cdmattr = createAttribute(attr);
            cdmvar.addAttribute(cdmattr);
        }
        if(cdmparentstruct != null)
            cdmparentstruct.addMemberVariable(cdmvar);
        else if(cdmgroup != null)
            cdmgroup.addVariable(cdmvar);

    }

    protected void
    createDimension(DapDimension dapdim, Group cdmgroup)
            throws DapException
    {
        if(dapdim.isShared()) {
            if(this.nodemap.containsKey(dapdim))
                throw new DapException("Attempt to declare dimension twice:" + dapdim.getFQN());
            Dimension cdmdim = new Dimension(dapdim.getShortName(), (int) dapdim.getSize(), true, false, false);
            this.nodemap.put(cdmdim,dapdim);
            cdmgroup.addDimension(cdmdim);
        } else
            throw new DapException("Attempt to declare an anonymous dimension");
    }

    protected Dimension
    createDimensionRef(DapDimension dim, Group cdmgroup)
            throws DapException
    {
        Dimension cdmdim = null;
        if(dim.isShared())
            cdmdim = (Dimension) this.nodemap.get(dim);
        else // anonymous
            cdmdim = new Dimension(null, (int) dim.getSize(), false, false, false);
        if(cdmdim == null)
            throw new DapException("Unknown dimension: " + dim.getFQN());
        return cdmdim;
    }

    protected EnumTypedef
    createEnum(DapEnumeration dapenum, Group cdmparent)
            throws DapException
    {
        DapType basetype = dapenum.getBaseType();
        DataType cdmbasetype = CDMTypeFcns.enumTypeFor(basetype);
        Map<Integer, String> map = new HashMap<Integer, String>();
        List<String> ecnames = dapenum.getNames();
        for(String ecname : ecnames) {
            DapEnumConst value = dapenum.lookup(ecname);
            map.put(value.getIntValue(), ecname);
        }

        EnumTypedef cdmenum = new EnumTypedef(dapenum.getShortName(),
                map, cdmbasetype);
        this.nodemap.put(cdmenum,dapenum);
        cdmparent.addEnumeration(cdmenum);
        return cdmenum;
    }

    /**
     * Our goal is to convert, where possible, to a
     * list of objects to a list of values
     * acceptable to the ucar.nc2.Attribute class
     *
     * @param dapattr The dap attribute whose values need to be converted
     * @return The created ucar.nc2.Attribute
     */
    protected Attribute
    createAttribute(DapAttribute dapattr)
    {
        return createAttribute(null, dapattr);
    }

    protected Attribute
    createAttribute(String prefix, DapAttribute dapattr)
    {
        Attribute cdmattr = null;
        switch (dapattr.getSort()) {
        case ATTRIBUTE:
            DapType basetype = dapattr.getBaseType();
            if(!(basetype.isLegalAttrType()))
                throw new ForbiddenConversionException("Illegal attribute type:" + basetype.toString());
            DataType cdmtype = CDMTypeFcns.daptype2cdmtype(basetype);
            DapEnumeration dapen = (basetype.isEnumType() ? (DapEnumeration)basetype : null);
            Object dapvalues = dapattr.getValues();
            Object cvt;
            if(dapen != null)
                cvt = Convert.convert(dapen,DapType.STRING,dapvalues);
            else
                cvt = Convert.convert(basetype,DapType.STRING,dapvalues);
            List cdmvalues = CDMTypeFcns.listify(cvt);
            EnumTypedef en = (EnumTypedef)(dapen  != null ? this.nodemap.get(basetype) : null);
            if(dapen != null) {
                cdmattr = new Attribute(dapattr.getShortName(), DataType.STRING);
                cdmattr.setValues(cdmvalues);
                cdmattr.setEnumType(en);
            }
            cdmattr = new Attribute(dapattr.getShortName(),cdmvalues, false);
            break;
        case ATTRIBUTESET:
            String setname = dapattr.getShortName();
            prefix = (prefix == null ? setname : prefix + "_" + setname);
            for(String key : dapattr.getAttributes().keySet()) {
                DapAttribute subattr = dapattr.getAttributes().get(key);
                cdmattr = createAttribute(prefix, subattr);
            }
            break;
        case OTHERXML:
            cdmvalues = new ArrayList();
            cdmvalues.add("OtherXML");
            cdmattr = new Attribute("OtherXML", cdmvalues);
            break;
        }
        this.nodemap.put(cdmattr,dapattr);
        return cdmattr;
    }

}