package nxm.sys.lib;

import java.util.Map;

import nxm.sys.inc.DataTypes;
import nxm.sys.inc.InternalUseOnly;

/** Class to support use of the <code>XMValue</code> data type added in X-Midas 4.6.0. This class
    provides a ground-up implementation of <code>XMValue</code> rather than just wrapping the
    underlying X-Midas C++ code. The advantage of a ground-up implementation is that this class
    can be used even if X-Midas is not available (e.g. when using XBC). <br>
    <br>
    In order to improve ease-of-use for Java and NeXtMidas users, some of the method names have
    been changed to match existing Java conventions. The following is a complete list of the
    changes:
    <table border="1">
      <caption style="visibility:hidden">""</caption>
      <tr><th>X-Midas Name                 </th><th>NeXtMidas Name                 </th></tr>
      <tr><td><code>as()               </code></td><td>n/a                             </td></tr>
      <tr><td><code>at(..)             </code></td><td><code>get(..)                </code></td></tr>
      <tr><td><code>assign(..)         </code></td><td><code>setValue(..)           </code></td></tr>
      <tr><td><code>data()             </code></td><td>n/a                             </td></tr>
      <tr><td><code>databytes()        </code></td><td>n/a                             </td></tr>
      <tr><td><code>erase(..)          </code></td><td><code>remove(..)             </code></td></tr>
      <tr><td><code>find(..)           </code></td><td><code>get(..)                </code></td></tr>
      <tr><td><code>has_key(..)        </code></td><td><code>containsKey(..)        </code></td></tr>
      <tr><td><code>is_legal_type(..)  </code></td><td><code>isLegalType(..)        </code></td></tr>
      <tr><td><code>is_numeric_type()  </code></td><td><code>isNumericType()        </code></td></tr>
      <tr><td><code>is_basic_type()    </code></td><td><code>isBasicType()          </code></td></tr>
      <tr><td><code>is_container()     </code></td><td><code>isContainer()          </code></td></tr>
      <tr><td><code>is_string_keyed()  </code></td><td><code>isStringKeyed()        </code></td></tr>
      <tr><td><code>impl()             </code></td><td>n/a                             </td></tr>
      <tr><td><code>prettyPrint(..)    </code></td><td><code>toString()             </code></td></tr>
      <tr><td><code>prettyString(..)   </code></td><td><code>toString()             </code></td></tr>
      <tr><td><code>push_back(..)      </code></td><td><code>append(..)             </code></td></tr>
      <tr><td><code>recast(..)         </code></td><td>n/a                             </td></tr>
      <tr><td><code>to()               </code></td><td><code>toD()</code>, <code>toF()</code>,<br>
                                                   <code>toX()</code>, <code>toL()</code>,<br>
                                                   <code>toI()</code>, <code>toB()</code>,<br>
                                                                   <code>toA()</code></td></tr>
      <tr><td><code>type()             </code></td><td><code>getFormatType()        </code></td></tr>
      <tr><td><code>type_name()        </code></td><td><code>getTypeName()          </code></td></tr>
      <tr><th>X-Midas Operator             </th><th>NeXtMidas Name                 </th></tr>
      <tr><td><code>operator=          </code></td><td><code>setValue(..)           </code></td></tr>
      <tr><td><code>operator==         </code></td><td><code>equals(..)             </code></td></tr>
      <tr><td><code>operator[]         </code></td><td><code>get(..)                </code></td></tr>
      <tr><td><code>operator&lt;&lt;   </code></td><td>n/a                             </td></tr>
      <tr><td><code>operator&gt;&gt;   </code></td><td>n/a                             </td></tr>
    </table><br>
    <br>
    Similarly, standard Java types are used in place of the X-Midas type definitions:
    <table border="1">
      <caption style="visibility:hidden">""</caption>
      <tr><th>X-Midas Type                      </th><th>NeXtMidas Type                    </th></tr>
      <tr><td><code>char*                    </code></td><td><code>String                   </code></td></tr>
      <tr><td><code>std::string              </code></td><td><code>String                   </code></td></tr>
      <tr><td><code>std::vector&lt;int_1&gt; </code></td><td><code>byte[]                   </code></td></tr>
      <tr><td><code>std::vector&lt;int_u1&gt;</code></td><td><code>byte[]                   </code></td></tr>
      <tr><td><code>std::vector&lt;int_2&gt; </code></td><td><code>short[]                  </code></td></tr>
      <tr><td><code>std::vector&lt;int_4&gt; </code></td><td><code>int[]                    </code></td></tr>
      <tr><td><code>std::vector&lt;int_8&gt; </code></td><td><code>long[]                   </code></td></tr>
      <tr><td><code>std::vector&lt;real_4&gt;</code></td><td><code>float[]                  </code></td></tr>
      <tr><td><code>std::vector&lt;real_8&gt;</code></td><td><code>double[]                 </code></td></tr>
      <tr><td><code>XMValue::List            </code></td><td><code>XMValue[]                </code></td></tr>
      <tr><td><code>XMValue::Table           </code></td><td><code>Map&lt;String,XMValue&gt;</code></td></tr>
      <tr><td><code>XMValue::KVPair          </code></td><td><code>KVPair                   </code></td></tr>
      <tr><td><code>XMValue::KVList          </code></td><td><code>KVPair[]                 </code></td></tr>
    </table><br>
    <br>

    @author  J. Czechowski IV
    @since   NeXtMidas 2.7.1
 */
public final class XMValue {
  /** Data Type: NONE (no data). */
  public static final char NONE    = ' ';
  /** Data Type: ASCII (ASCII string, same as {@link DataTypes#ASCII}). */
  public static final char ASCII   = (char)DataTypes.ASCII;
  /** Data Type: ASCII (ASCII string, same as {@link DataTypes#ASCII256}). */
  public static final char ASCII256 = (char)DataTypes.ASCII256; //'Z' new type NXM-3570
  /** Data Type: INT_1 (1-byte signed integer, same as {@link DataTypes#BYTE}). */
  public static final char INT_1   = (char)DataTypes.BYTE;
  /** Data Type: INT_U1 (1-byte unsigned integer, limited NeXtMidas support). */
  public static final char INT_U1  = 'O';
  /** Data Type: INT_2 (2-byte signed integer, same as {@link DataTypes#INT}). */
  public static final char INT_2   = (char)DataTypes.INT;
  /** Data Type: INT_4 (4-byte signed integer, same as {@link DataTypes#LONG}). */
  public static final char INT_4   = (char)DataTypes.LONG;
  /** Data Type: INT_8 (8-byte signed integer, same as {@link DataTypes#XLONG}). */
  public static final char INT_8   = (char)DataTypes.XLONG;
  /** Data Type: REAL_4 (4-byte IEEE754 floating point number, same as {@link DataTypes#FLOAT}). */
  public static final char REAL_4  = (char)DataTypes.FLOAT;
  /** Data Type: REAL_8 (8-byte IEEE754 floating point number, same as {@link DataTypes#DOUBLE}). */
  public static final char REAL_8  = (char)DataTypes.DOUBLE;
  /** Data Type: LIST (non-homogeneous list of XMValues). */
  public static final char LIST    = (char)DataTypes.XMVALUE_LIST;
  /** Data Type: KVLIST (list of keyed XMValues, not the same as {@link KeyVector}). */
  public static final char KVLIST  = (char)DataTypes.XMVALUE_KVLIST;
  /** Data Type: TABLE (table of keyed XMValues, not the same as {@link DataTypes#TABLE} or {@link Table}). */
  public static final char TABLE   = (char)DataTypes.XMVALUE_TABLE;
  /** Type specifier for a generic XMValue. {@link #getFormatType} will always return a specific
      type and will never return {@link #XMVALUE}. This is only useful when converting to an XMValue.
   */
  public static final char XMVALUE = (char)DataTypes.XMVALUE;

  /** all valid type letters 'Z' new type NXM-3570 */
  @InternalUseOnly()
  private static final String legalTypes = " ABOILXFDCKHZ";

  private static Class<? extends Backend> defaultBackend = XMValueJava.class;
  /** the basic implementation */
  private Backend backend;

  /** Creates a new instance with an initial value of {@link #NONE}.
      @param clazz Any class that extends Backend
  */
  public XMValue (Class<? extends Backend> clazz) {
    try {
      this.backend = clazz.getConstructor().newInstance(); // Updated in 3.9.0 to use default constructor
    }
    catch (Exception e) {
      throw new RuntimeException("Can not instantiate "+backend, e);
    }
  }

  /** <b>Internal use only, this method should only be used internal to SYS and XBC, do not use.</b>
      @param backend Some object that implements the Backend interface
  */
  public XMValue (Backend backend) {
    this.backend = backend;
  }

  /** <b>Internal use only, this method should only be used internal to SYS and XBC, do not use.</b>
      @return backend Returns the current Backend interface
  */
  public Backend getBackend () {
    return backend;
  }

  /** <b>Internal use only, this method should only be used internal to SYS and XBC, do not use.</b>
     @param b Some object that implements the Backend interface
  */
  public void setBackend (Backend b) {
    if (b == null) throw new NullPointerException("Illegal XMValue.setBackend(null)");
    backend = b;
  }

  /** <b>Internal use only, this method should only be used internal to SYS and XBC, do not use.</b>
      @param clazz Any class that extends Backend
  */
  public static void setDefaultBackend (Class<? extends Backend> clazz) {
    defaultBackend = clazz;
  }

  /** <b>Internal use only, this method should only be used internal to SYS and XBC, do not use.</b>
      @return defaultBackend returns the method definitions for Backend
  */
  public static Class<? extends Backend> getDefaultBackend () {
    return defaultBackend;
  }

  /** <b>Internal use only, this method should only be used internal to SYS and XBC, do not use.</b>
      @param obj The object being passed in to convert
      @return XMValue Returns a converted object
  */
  public static XMValue toXMValue (Object obj) {
    return toXMValue(obj, ' ', '_', null);
  }

  /** <b>Internal use only, this method should only be used internal to SYS and XBC, do not use.</b>
      @param obj The object being passed in to convert
      @param ref The reference object (often a Midas context or a Command)
      @return XMValue Returns a converted object
  */
  public static XMValue toXMValue (Object obj, Object ref) {
    return toXMValue(obj, ' ', '_', ref);
  }

  /** <b>Internal use only, this method should only be used internal to SYS and XBC, do not use.</b>
      @param obj      The object being passed in to convert
      @param mode     The mode of the new object (note that mode='V' and mode='3' both set SPA=3).
      @param type     The type of the object being passed in
      @param ref      The reference object (often a Midas context or a Command)
      @return XMValue Returns a converted object
   */
  public static XMValue toXMValue (Object obj, char mode, char type, Object ref) {
    char givenType = type;

    if (obj instanceof XMValue) {
      XMValue v = (XMValue)obj;
      if ((type == '_') || (type == XMVALUE) || (v.getFormatType() == type)) {
        return v; // fine as-is
      }
    }

    if ((type == '_') || (type == XMVALUE)) {
      if ((obj == null) || obj.toString().equalsIgnoreCase("None")) {
        type = NONE;
      }
      else {
        type = Convert.guessType(obj,'_','S');
        if (type == '_') {
          // try harder for strings
          obj  = Convert.o2o(obj, '_', ref);
          type = Convert.guessType(obj,'S','S');
        }
      }
    }

    if (isNumericType(type) || (type == 'N')) {
      if (obj == null) return new XMValue(0);
      Data val = (Data)Convert.o2o(obj, type, ref); // Any non-null numeric should result in a Data instance
      return new XMValue(val);
    }
    else if (type == NONE) {
      return new XMValue();
    }
    else if ((Data.isString((byte)type)) || (type == 'S')) {
      return new XMValue(obj.toString());
    }
    else if (type == LIST) {
throw new MidasException("Converstion from Object->XMValue for TYPE="+type+" not supported yet.");

    }
    else if (type == KVLIST) {
throw new MidasException("Converstion from Object->XMValue for TYPE="+type+" not supported yet.");

    }
    else if ((type == TABLE) || (type == 'T')) {
// This isn't the cleanest way of doing this, but it works for now...
      XMValue v = new XMValue(TABLE);
      Backend b = v.getBackend();
      Table   t = Convert.o2t(obj,ref);

      for (String key : t.getKeys()) {
        XMValue val = toXMValue(t.get(key));
        b.add(key, val);
      }
      return v;
    }
    else if (givenType == '_') { // no matches -> same as ASCII
      return new XMValue(obj.toString());
    }
    else {
      throw new IllegalArgumentException("Invalid type '"+type+"' expected one of '"+legalTypes+"'.");
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Public API
  //////////////////////////////////////////////////////////////////////////////////////////////////

  /** Creates a new instance with an initial value of {@link #NONE}. */
  public XMValue () { this(defaultBackend); }

  /** Creates a new instance with the given initial type.
      @param type The initial type.
   */
  public XMValue (char  type) { this(); setFormatType(type); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (Data     v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (String   v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (byte     v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (short    v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (int      v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (long     v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (float    v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (double   v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (byte[]   v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (short[]  v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (int[]    v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (long[]   v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (float[]  v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (double[] v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (XMValue[] v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (KVPair[] v) { this(); setValue(v); }

  /** Creates a new instance with the given initial value.
      @param v The initial value.
   */
  public XMValue (Map<String,XMValue> v) { this(); setValue(v); }


  /** Compares an object to this one. Note that this method is not always the most efficient due to
      the fact all XMValues regardless of their implementation backend all compare the same.
      @param obj The object.
      @return true if equal, false otherwise.
   */
  @Override
  public final boolean equals (Object obj) {
    if (!(obj instanceof XMValue)) return false;

    XMValue v = (XMValue)obj;
    char    t = getFormatType();
    int     s = getSize();

    if (t != v.getFormatType()) return false;
    if (s != v.getSize()) return false;
    if (s == 0          ) return true;  // no further checks needed
    if (t == NONE       ) return true;  // no further checks needed
    if (t == ASCII      ) return toA().equals(v.toA());

    if (isNumericType(t)) {
      if (backend.isScalar() != v.backend.isScalar()) return false;
    }

    if ((s == 1) && isNumericType(t)) {
      switch (t) {
        case INT_1:  return (toB() == v.toB());
        case INT_U1: return (toL() == v.toL()); // <-- tbd
        case INT_2:  return (toI() == v.toI());
        case INT_4:  return (toL() == v.toL());
        case INT_8:  return (toX() == v.toX());
        case REAL_4: return Float.valueOf(toF()).equals(v.toF());
        case REAL_8: return Double.valueOf(toD()).equals(v.toD());
        default:     throw new AssertionError("Invalid XMValue type '"+t+"'.");
      }
    }

    if (t == LIST) {
      for (int i = 1; i <= s; i++) {
        if (!get(i).equals(v.get(i))) return false;
      }
      return true;
    }

    if ((t == KVLIST) || (t == TABLE)) {
      XMValue v1 = (t == TABLE)? items()   : this;
      XMValue v2 = (t == TABLE)? v.items() : v;

      for (int i = 1; i <= s; i++) {
        KVPair e1 = v1.backend.getPair(i);
        KVPair e2 = v2.backend.getPair(i);

        if (!e1.getKey().equals(e2.getKey()) || !e1.getValue().equals(e2.getValue())) {
          if (t == TABLE) delete(v1,v2); // free any native pointers
          return false;
        }
      }
      // free any native pointers - - XMValue can be XMValueNative
      if (t == TABLE) delete(v1,v2);
      return true;
    }

    throw new AssertionError("Invalid XMValue type '"+t+"'.");
  }

  /** Generates a hash code for this object. Note that this method is not always the most efficient
      due to the fact all XMValues regardless of their implementation backend all use the same hash
      code algorithm.
      @return A hash code for the object.
   */
  @Override
  public final int hashCode () {
    char t = getFormatType();
    int  s = getSize();

    if (t == ASCII) {
      return toA().hashCode();
    }
    if (s == 0) {
      return t;
    }
    if (s == 1) {
      if (t == INT_8) {
        long bits = toX();
        return (int)(bits ^ (bits >>> 32));
      }
      if (t == REAL_8) {
        long bits = Double.doubleToLongBits(toD());
        return (int)(bits ^ (bits >>> 32));
      }
      if (isNumericType(t)) {
        switch (t) {
          case INT_1:  return INT_1  ^ toB();
          case INT_U1: return INT_U1 ^ toL(); // <-- tbd
          case INT_2:  return INT_2  ^ toI();
          case INT_4:  return INT_4  ^ toL();
          case REAL_4: return Float.floatToIntBits(toF());
          default:     throw new AssertionError("Invalid XMValue type '"+t+"'.");
        }
      }
    }
    if (t == LIST) {
      int hash = LIST;
      for (int i = 1; (i <= s) && (i <= 8); i++) {
        hash = hash ^ (get(i).hashCode() << 4*(i-1));
      }
      return hash;
    }
    if ((t == KVLIST) || (t == TABLE)) {
      XMValue v = (t == TABLE)? items() : this;
      int hash = t;
      for (int i = 1; (i <= s) && (i <= 8); i++) {
        KVPair e = v.backend.getPair(i);
        hash = hash ^ (e.getKey().hashCode() << 4*(i-1)) ^ (e.getValue().hashCode() << 4*(i-1));
      }
      // free any native pointer - XMValue can be XMValueNative
      if (t == TABLE) delete(v);
      return hash;
    }

    throw new AssertionError("Invalid XMValue type '"+t+"'.");
  }

  /** Gets the string representing this value.
      @return A string representing the value.
   */
  @Override
  public String toString () {
    char type = getFormatType();
    int  size = getSize();

    if (type == NONE ) return "None";
    if (type == ASCII) return toA();
    if (type == ASCII256) return toZ();

    if ((size == 1) && isNumericType(type)) {
      switch (type) {
        case INT_1:  return Byte.toString(toB());
        case INT_U1: return Integer.toString(toL()); // <-- tbd
        case INT_2:  return Short.toString(toI());
        case INT_4:  return Integer.toString(toL());
        case INT_8:  return Long.toString(toX());
        case REAL_4: return Float.toString(toF());
        case REAL_8: return Double.toString(toD());
        default:     throw new AssertionError("Invalid XMValue type '"+type+"'.");
      }
    }

    if (isNumericType(type) || (type == LIST)) {
      char    open     = (type == LIST)? '[' : '(';
      char    close    = (type == LIST)? ']' : ')';
      boolean showType = (type == LIST);

      StringBuilder str = new StringBuilder();
      str.append(open);
      for (int i = 1; i <= size; i++) {
        XMValue val = get(i);
        if (i  !=  1) str.append(',');
        if (showType) str.append(val.getFormatType()).append(':');

        str.append(val);
      }
      str.append(close);
      return str.toString();
    }

    if ((type == KVLIST) || (type == TABLE)) {
      XMValue items = (type == KVLIST)? this : items();

      StringBuilder str = new StringBuilder();
      str.append('{');
      for (int i = 1; i <= size; i++) {
        KVPair pair = items.backend.getPair(i);
        String  key = pair.getKey();
        XMValue val = pair.getValue();

        if (i != 1) str.append(',');
        str.append(val.getFormatType()).append(':');
        str.append(key).append('=');
        str.append(val);
      }
      str.append('}');
      // free any native pointers - XMValue can be XMValueNative
      if (type != KVLIST) delete(items);
      return str.toString();
    }

    throw new AssertionError("Invalid XMValue type '"+type+"'.");
  }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (XMValue v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (Data   v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (String v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (byte v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (short v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (int v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (long v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (float v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (double v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (byte[] v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (short[] v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (int[] v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (long[] v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (float[] v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (double[] v) { backend.setValue(v); }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public final void setValue (XMValue[] v) {
    backend.setValue(LIST);
    for (XMValue e : v) {
      backend.add(e);
    }
  }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public final void setValue (KVPair[] v) {
    backend.setValue(KVLIST);
    for (KVPair e : v) {
      backend.add(e.getKey(), e.getValue());
    }
  }

  /** Sets the underlying value and type.
      @param v The new value.
   */
  public void setValue (Map<String,XMValue> v) {
    backend.setValue(TABLE);
    for (Map.Entry<String,XMValue> e : v.entrySet()) {
      backend.add(e.getKey(), e.getValue());
    }
  }

  /** Sets the underlying value and type. If type is is a numeric type, an empty vector of that
      type. All other types create a default constructed instance of that type.
      @param type The new type.
   */
  public final void setFormatType (char type) {
    if (!isLegalType(type)) {
      throw new IllegalArgumentException("Invalid type '"+type+"' expected one of '"+legalTypes+"'.");
    }
    backend.setValue(type);
  }

  /** Converts this data object to a new data object. This is only used by Convert.o2o(..) and
      Convert.s2o(..).
   */
  XMValue convertTo (int newSize, int newSPA, char newMode, char newType) { // package-private
    char type = getFormatType();
    int  size = getSize();

    if (newSize == -1 )  { newSize = size; }
    if (newSPA  == -1 )  { newSPA  = 1; newMode = 'S'; }
    if (newType == '_')  { newType = type; }

    if (type == newType) return this;
    XMValue val = copy();
    val.setFormatType(newType);
    return val;
  }

  /** Sets the underlying value and type.
      @param type  The new type.
      @param value The new value.
      @throws UnsupportedOperationException If the type is KVLIST or TABLE.
   */
  public final void setValue (char type, double value) throws UnsupportedOperationException {
    setValue(type,1,value);
  }

  /** Sets the underlying value and type.
      @param type  The new type.
      @param size  The number of elements.
      @param value The new value.
      @throws UnsupportedOperationException If the type is KVLIST or TABLE.
   */
  public final void setValue (char type, int size, double value) throws UnsupportedOperationException {
    if (!isLegalType(type)) {
      throw new IllegalArgumentException("Invalid type '"+type+"' expected one of '"+legalTypes+"'.");
    }
    if (isStringKeyed()) {
      throw new UnsupportedOperationException("Can not use XMValue.get(int) with type "+getTypeName()+".");
    }
    backend.setValue(type,size,value);
  }

  /** Return the type of the underlying XMValue.
      @return The type of the underlying XMValue.
   */
  public char getFormatType () { return backend.getFormatType(); }

  /** Gets the full string name for the given XMValue type.
      @return The type name.
   */
  public final String getTypeName () { return getTypeName(getFormatType()); }

  /** Indicates if underlying value is a numeric type (either a single number or a vector of numbers).
      @return true if numeric, false otherwise.
   */
  public final boolean isNumericType () { return isNumericType(getFormatType()); }

  /** Indicates if underlying value is one of the basic X-Midas types (numeric or ASCII).
      @return true if a basic type, false otherwise.
   */
  public final boolean isBasicType () { return isBasicType(getFormatType()); }

  /** Indicates if underlying value is a container that can contain other XMValues.
      @return true if a container, false otherwise.
   */
  public final boolean isContainer () { return isContainer(getFormatType()); }

  /** Indicates if underlying value is a string-keyed container.
      @return true if a string-keyed container, false otherwise.
   */
  public final boolean isStringKeyed () { return isStringKeyed(getFormatType()); }

  /** Indicates if underlying value is a numeric vector, a {@link #LIST} or {@link #KVLIST}.
      @return true if a vector or list, false otherwise.
   */
  private final boolean isVectorOrList () {
    char type = getFormatType();
    return (isNumericType(getFormatType()) && !backend.isScalar()) || (type == LIST) || (type == KVLIST);
  }

  /** Returns a copy of this value.
      @return A copy of this value.
   */
  public final XMValue copy () {
    XMValue v = new XMValue(backend.getClass());
    v.setValue(this);
    return v;
  }

  /** Gets the size.
      <pre>
        - For strings, the length of the string.
        - For arrays of numeric values, the number of values.
        - For single numbers, 1.
        - For Lists and Tables, the number of top level entries.
        - For {@link #NONE}, 0.
      </pre>
      @return The number of elements (this is not a recursive count).
   */
  public int size () { return backend.size(); }

  /** Gets the size. This is the same as <code>size()</code>.
      @return The number of elements (this is not a recursive count).
   */
  public final int getSize () { return backend.size(); }

  /** Is the value empty? This is the same as checking <code>(size() == 0)</code>.
      @return true if empty, false otherwise.
   */
  public final boolean isEmpty () { return (backend.size() == 0); }

  /** Gets the list or vector element at the specified index. <br>
      <br>
      Per X-Midas documentation: <i>This is not particularly efficient, but is useful when you
      are doing a relatively scarce number of look-ups in a type-free manner.</i>
      @param i The index. <b>This is a one-based value.</b>
      @return The value at that index.
      @throws UnsupportedOperationException if the underlying type is not a vector or list.
      @throws ArrayIndexOutOfBoundsException if i&lt;0 or i&gt;=size()
   */
  public final XMValue get (int i) throws UnsupportedOperationException, ArrayIndexOutOfBoundsException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.get(int) with a TABLE.");
    }
    if ((i < 1) || (i > size())) {
      throw new ArrayIndexOutOfBoundsException("Can not get element "+i+" in XMValue with only "+size()+" elements.");
    }
    return backend.get(i);
  }

  /** Gets the KVList or Table entry by name. If this is not a {@link #KVLIST} or a {@link #TABLE},
      this is the same as <code>get(Convert.s2l(key))</code>. <br>
      <br>
      Per X-Midas documentation: <i>This is not particularly efficient, but is useful when you
      are doing a relatively scarce number of look-ups in a type-free manner.</i>
      @param key The key,
      @return The value with that key.
   */
  public final XMValue get (String key) {
    if (isStringKeyed()) return backend.get(key);
    else                 return get(Convert.s2l(key));
  }

  /** set XMValue
   *
   *  @param i   index
   *  @param val value
   */
  public final void set (int i, XMValue val) {
    char type = getFormatType();

    if (type == KVLIST) {
      if (!val.isStringKeyed() && (val.size() != 1)) {
        throw new UnsupportedOperationException("Can only use XMValue.set(int,XMValue) on a KVLIST if "
                                               +"the value passed in is a TABLE or KVLIST os size 1; "
                                               +"given "+val.getTypeName()+" of size "+val.size()+".");
      }
      KVPair pair = val.backend.getPair(0);
      backend.set(i, pair.getKey(), pair.getValue());
      return;
    }

    if ((i == 1) && (type == ASCII)) {
      backend.setValue(val.toA());
      return;
    }

    if ((i < 1) || (i > size())) {
      throw new ArrayIndexOutOfBoundsException("Can not set element "+i+" in XMValue with only "+size()+"elements.");
    }
    if (!isVectorOrList()) {
      throw new UnsupportedOperationException("Can not use XMValue.set(int,XMValue) with type "+getTypeName()+".");
    }
    backend.set(i, val);
  }

  /** set XMValue
  *
  *  @param key key
  *  @param val value
  */
  public final void set (String key, XMValue val) {
    if (isStringKeyed()) backend.put(key, val);
    else                 set(Convert.s2l(key),val);
  }

  /** put XMValue
   *
   *  @param key key
   *  @param val value
   */
  public final void put (String key, XMValue val) {
    set(key,val);
  }

  /** Set XMValue
   *
   *  @param i   index
   *  @param val value
   *  @return XMValue
   */
  public final XMValue set (int i, Object val) {
    XMValue v = toXMValue(val);
    set(i, v);
    return v;
  }

  /** Set XMValue
   *
   *  @param key key
   *  @param val value
   *  @return XMValue
   */
  public final XMValue set (String key, Object val) {
    XMValue v = toXMValue(val);
    set(key, v);
    return v;
  }

  /** Indicates if a given key is contained in the KVList or Table entry by name. This is not
      particularly efficient, but is useful when you are doing a relatively scarce number of
      look-ups in a type-free manner.
      @param key The key,
      @return true if found, false otherwise.
      @throws UnsupportedOperationException if the underlying type is not keyed.
   */
  public final boolean containsKey (String key) throws UnsupportedOperationException {
    if (!isStringKeyed()) {
      throw new UnsupportedOperationException("Can not use XMValue.containsKey(String) with type "+getTypeName()+".");
    }
    return backend.containsKey(key);
  }

  /** A type-free way of appending values to a vector of numbers or XMValues to an List.
      @param v The value to append.
      @throws UnsupportedOperationException If the underlying type is not a vector of numbers
              or a list.
   */
  public final void append (XMValue v) throws UnsupportedOperationException {
    if (!isVectorOrList()) {
      throw new UnsupportedOperationException("Can not use XMValue.append(XMValue) with type "+getTypeName()+".");
    }
    backend.add(v);
  }

  /** A type-free way of appending values to a KVList or a Table.
      @param v The value to append.
      @throws UnsupportedOperationException If the underlying type is not a KVList or a Table.
   */
  public final void append (KVPair v) throws UnsupportedOperationException {
    if (!isStringKeyed()) {
      throw new UnsupportedOperationException("Can not use XMValue.append(KVPair) "
                                            + "with type "+getTypeName()+".");
    }
    backend.add(v.getKey(), v.getValue());
  }

  /** Merges two Lists or KVLists together. This performs a concatenation operation on the two
      lists -- as opposed to {@link #append(XMValue)} which appends the given list as a single
      sub-list (increasing the length of the original list by exactly one). <br>
      <br>
      Lists can only be merged with other Lists. KVLists can only be merged with other KVLists.
      @param v The list to merge with the current list.
      @throws UnsupportedOperationException If the underlying types do not match.
   */
  public final void merge (XMValue v) throws UnsupportedOperationException {
    char tt = this.getFormatType();
    char vt = v.getFormatType();
    if (!isStringKeyed(tt) || !isStringKeyed(vt)) {
      throw new UnsupportedOperationException("Incompatible types for XMValue.merge(XMValue) "
                                              +getTypeName(tt)+" and "+getTypeName(vt)+".");
    }

    XMValue prevVal = v; // save previous value so can free pointer
    if (v.getFormatType() != KVLIST) v = v.items();
    Backend b = v.backend;
    for (int i = 0; i < v.size(); i++) {
      KVPair pair = b.getPair(i);
      backend.add(pair.getKey(), pair.getValue());
    }
    // make sure native pointers are freed - XMValue can be XMValueNative
    if (prevVal != v) delete(prevVal);
    delete(v);
  }

  /** Updates a Table with the keys/values from the provided Table or KVList -- overwriting any
      existing keys with matching names.
      @param v The Table or KVList to merge with the current Table.
      @throws UnsupportedOperationException If this is not a Table or if v is not a Table or KVList.
   */
  public final void update (XMValue v)  throws UnsupportedOperationException {
    char tt = this.getFormatType();
    char vt = v.getFormatType();
    if (!isStringKeyed(tt) || !isStringKeyed(vt)) {
      throw new UnsupportedOperationException("Incompatible types for XMValue.update(XMValue) "
                                              +getTypeName(tt)+" and "+getTypeName(vt)+".");
    }

    XMValue prevVal = v; // save off so can free pointer
    if (v.getFormatType() != KVLIST) v = v.items();
    Backend b = v.backend;
    for (int i = 0; i < v.size(); i++) {
      KVPair pair = b.getPair(i);
      backend.put(pair.getKey(), pair.getValue());
    }
    // make sure native pointers are freed - XMValue can be XMValueNative
    if (prevVal != v) delete(prevVal);
    delete(v);
  }

  /** Clears the value. For strings, Lists and Tables, the underlying data is cleared, for single
      numbers, the value is set to zero, for a {@link #NONE} this is a no-op.
   */
  public void clear () {
    backend.clear();
  }

  /** Gets a vector version of this XMValue, if this XMValue is a single numeric value returns a
      new single element vector of the same type. If it is already a vector (including an List or
      KVList), returns a copy of itself.
      @return A copy of the data as a "vector."
      @throws UnsupportedOperationException If this is not a single numeric value, a List or a KVList.
   */
  public final XMValue vectorize () throws UnsupportedOperationException {
    char t = getFormatType();
    if (t == LIST  ) return copy();
    if (t == KVLIST) return copy();

    if (!isNumericType(t)) {
      throw new UnsupportedOperationException("Can not use XMValue.vectorize() with type "+getTypeName()+".");
    }

    return (backend.isScalar())? backend.vectorize() : copy();
  }

  /** Get a Table version of this XMValue, if this XMValue is a KVList, then we iterate through the
      (key,value) pairs and add them to a new table which is returned. If keys are repeated, later
      ones in the list replace earlier ones. If this XMValue is already a table, returns a copy.
      @return A copy of the data in this XMValue as a table.
      @throws UnsupportedOperationException if this is not a Table or KVList.
   */
  public final XMValue tablize () throws UnsupportedOperationException {
    if (!isStringKeyed()) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.tablize();
  }

  /** Gets a List of the keys in an Table or KVList.
      @return A copy of the keys.
      @throws UnsupportedOperationException if this is not a Table or KVList.
   */
  public final XMValue keys () throws UnsupportedOperationException {
    if (!isStringKeyed()) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.keys();
  }

  /** Gets a KVList of the (key,value) pairs in a table. If this XMValue is already an KVList,
      return a copy.
      @return A copy of the items.
      @throws UnsupportedOperationException if this is not a Table or KVList.
   */
  public final XMValue items () throws UnsupportedOperationException {
    if (!isStringKeyed()) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.items();
  }

  /** Return this XMValue as a single numeric value, does not change the XMValue itself. For
      vectors (including List), only the first element is converted and returned; if the vector
      is empty, an exception is raised. A numeric conversion on a None value always returns 0.
      Tables cannot be converted to numeric values.
      @return The value, properly converted.
      @throws UnsupportedOperationException if this is a table.
   */
  public final double toD () throws UnsupportedOperationException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.toD();
  }

  /** Return this XMValue as a single numeric value, does not change the XMValue itself. For
      vectors (including List), only the first element is converted and returned; if the vector
      is empty, an exception is raised. A numeric conversion on a None value always returns 0.
      Tables cannot be converted to numeric values.
      @return The value, properly converted.
      @throws UnsupportedOperationException if this is a table.
   */
  public final float toF () throws UnsupportedOperationException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.toF();
  }

  /** Return this XMValue as a single numeric value, does not change the XMValue itself. For
      vectors (including List), only the first element is converted and returned; if the vector
      is empty, an exception is raised. A numeric conversion on a None value always returns 0.
      Tables cannot be converted to numeric values.
      @return The value, properly converted.
      @throws UnsupportedOperationException if this is a table.
   */
  public final long toX () throws UnsupportedOperationException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.toX();
  }

  /** Return this XMValue as a single numeric value, does not change the XMValue itself. For
      vectors (including List), only the first element is converted and returned; if the vector
      is empty, an exception is raised. A numeric conversion on a None value always returns 0.
      Tables cannot be converted to numeric values.
      @return The value, properly converted.
      @throws UnsupportedOperationException if this is a table.
   */
  public final int toL () throws UnsupportedOperationException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.toL();
  }

  /** Return this XMValue as a single numeric value, does not change the XMValue itself. For
      vectors (including List), only the first element is converted and returned; if the vector
      is empty, an exception is raised. A numeric conversion on a None value always returns 0.
      Tables cannot be converted to numeric values.
      @return The value, properly converted.
      @throws UnsupportedOperationException if this is a table.
   */
  public final short toI () throws UnsupportedOperationException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.toI();
  }

  /** Return this XMValue as a single numeric value, does not change the XMValue itself. For
      vectors (including List), only the first element is converted and returned; if the vector
      is empty, an exception is raised. A numeric conversion on a None value always returns 0.
      Tables cannot be converted to numeric values.
      @return The value, properly converted.
      @throws UnsupportedOperationException if this is a table.
   */
  public final byte toB () throws UnsupportedOperationException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.toB();
  }

  /** Return this XMValue as a string, does not change the XMValue itself. For vectors (including
      List), only the first element is converted and returned; if the vector is empty, an exception
      is raised. A numeric conversion on a None value always returns 0. Tables cannot be converted.
      @return The value, properly converted.
      @throws UnsupportedOperationException if this is a table.
   */
  public final String toA () throws UnsupportedOperationException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.toA();
  }

  /** Return this XMValue as a string, does not change the XMValue itself. For vectors (including
      List), only the first element is converted and returned; if the vector is empty, an exception
      is raised. A numeric conversion on a None value always returns 0. Tables cannot be converted.
      @return The value, properly converted.
      @throws UnsupportedOperationException if this is a table.
  */
  public final String toZ () throws UnsupportedOperationException {
    if (getFormatType() == TABLE) {
      throw new UnsupportedOperationException("Can not use XMValue.tablize() with type "+getTypeName()+".");
    }
    return backend.toZ();
  }

  /** Convert XMValue to Java Number
   *  @return  XMValue as Java Number
   *  @since NeXtMidas 3.9.0 for NXM-3157
   */
  public Number toNumber() {
    switch (getFormatType()) {
      case DataTypes.BYTE:   return toB();
      case DataTypes.INT:    return toI();
      case DataTypes.LONG:   return toL();
      case DataTypes.XLONG:  return toX();
      case DataTypes.FLOAT:  return toF();
      case DataTypes.DOUBLE: return toD();
      default:   return toD();
    }
  }

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

  /** Gets the full string name for the given XMValue type.
      @param type The type character
      @return The type name or "Unknown" if unknown.
   */
  public static String getTypeName (char type) {
    switch (type) {
      case NONE:     return "None";
      case ASCII:    return "ASCII";
      case ASCII256: return "ASCII256"; // 3.9.4 NXM-3570
      case INT_1:    return "int_1";
      case INT_U1:   return "int_u1";
      case INT_2:    return "int_2";
      case INT_4:    return "int_4";
      case INT_8:    return "int_8";
      case REAL_4:   return "real_4";
      case REAL_8:   return "real_8";
      case LIST:     return "List";
      case KVLIST:   return "KVList";
      case TABLE:    return "Table";
      default:       return "Unknown";
    }
  }

  /** Indicates if given type is a numeric type (either a single number or a vector of numbers).
      @param type The type character
      @return true if numeric, false otherwise.
   */
  public static boolean isNumericType (char type) {
    switch (type) {
      case INT_1:  return true;
      case INT_U1: return true;
      case INT_2:  return true;
      case INT_4:  return true;
      case INT_8:  return true;
      case REAL_4: return true;
      case REAL_8: return true;
      default:     return false;
    }
  }

  /** Indicates if given type value is one of the basic X-Midas types (numeric or ASCII).
      @param type The type character
      @return true if a basic type, false otherwise.
   */
  public static boolean isBasicType (char type) {
    return (type == ASCII) || (type == ASCII256) || isNumericType(type); // added ASCII256 3.9.4 NXM3570
  }

  /** Indicates if given type value is a container that can contain other XMValues.
      @param type The type character
      @return true if a container, false otherwise.
   */
  public static boolean isContainer (char type) {
    return (type == LIST) || (type == KVLIST) || (type == TABLE);
  }

  /** Indicates if given type value is a string-keyed container.
      @param type The type character
      @return true if a string-keyed container, false otherwise.
   */
  public static boolean isStringKeyed (char type) {
    return (type == KVLIST) || (type == TABLE);
  }

  /** Returns whether the type tag given is a legal XMValue type. Useful for de-serialization
      routines that might encounter types from an incompatible X-Midas version.
      @param type The type character
      @return true if legal, false otherwise.
   */
  public static boolean isLegalType (char type) {
    return (legalTypes.indexOf(type) >= 0);
  }
  /** Frees any XMValue objects with an XMValueNative Backend from the heap.
  *
  *  @param xmValues The list of XMValue objects to be freed from the heap
  *  @since NeXtMidas 4.1.3
  */
 public static void delete (XMValue ... xmValues) {
   for (XMValue xmVal : xmValues) xmVal.delete();
 }

 /** Frees any pointer associated with the XMValue object
  *  (needed if it backend is XMValue) */
 public void delete () {
   backend.delete();
 }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Nested Classes
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** A Key-Value Pair. */
  public static final class KVPair implements Map.Entry<String,XMValue> {
    private String  key;
    private XMValue val;

    /** Creates a new instance from a {@link java.util.Map.Entry}.
        @param entry The given entry.
     */
    public KVPair (Map.Entry<String,XMValue> entry) {
      this.key = entry.getKey();
      this.val = entry.getValue();
    }

    /** Creates a new instance.
        @param key The key.
        @param val The value.
     */
    public KVPair (String key, XMValue val) {
      this.key = key;
      this.val = val;
    }

    @Override
    public String  getKey ()   { return key; }
    @Override
    public XMValue getValue () { return val; }
    @Override
    public String  toString () { return key+"="+val; }

    @Override
    public boolean equals (Object o) {
      // Taken from the javadocs for Map.Entry
      if (!(o instanceof Map.Entry)) return false;
      Map.Entry<?,?> e1 = this;
      Map.Entry<?,?> e2 = (Map.Entry<?,?>)o;
      return (e1.getKey()==null ? e2.getKey()==null : e1.getKey().equals(e2.getKey()))  &&
             (e1.getValue()==null ? e2.getValue()==null : e1.getValue().equals(e2.getValue()));
    }

    @Override
    public int hashCode () {
      // Taken from the javadocs for Map.Entry
      return (getKey()==null   ? 0 : getKey().hashCode()) ^
             (getValue()==null ? 0 : getValue().hashCode());
    }

    /** Not supported, always throws {@link UnsupportedOperationException}.
        @param v not used
        @return not used
        @throws UnsupportedOperationException Always
     */
    @Override
    public XMValue setValue (XMValue v) throws UnsupportedOperationException {
      throw new UnsupportedOperationException("setValue(XMValue) not supported");
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // XMValue.Backend Interface
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** This is the backend implementation used by XMValue. <b>This interface is for internal use
      only and is subject to change without notice.</b> The complete public XMValue API is in
      {@link XMValue}.
   */
  public interface Backend {
    /** Set value with String parameter
        @param v String value
     */
    void setValue (String v);
    /** Set value with byte parameter
        @param v byte value
     */
    void setValue (byte v);
    /** Set value with short parameter
        @param v short value
     */
    void setValue (short v);
    /** Set value with int parameter
        @param v int value
     */
    void setValue (int v);
    /** Set value with long parameter
        @param v long value
     */
    void setValue (long v);
    /** Set value with float parameter
        @param v float value
     */
    void setValue (float v);
    /** Set value with double parameter
        @param v double value
     */
    void setValue (double v);
    /** Set value with byte[] parameter
        @param v byte[] value
     */
    void setValue (byte[] v);
    /** Set value with short[] parameter
        @param v short[] value
     */
    void setValue (short[] v);
    /** Set value with int[] parameter
        @param v int[] value
     */
    void setValue (int[] v);
    /** Set value with long[] parameter
        @param v long[] value
     */
    void setValue (long[] v);
    /** Set value with float[] parameter
        @param v float[] value
     */
    void setValue (float[] v);
    /** Set value with double[] parameter
        @param v double[] value
     */
    void setValue (double[] v);
    /** Set value with XMValue parameter
        @param v XMValue value
     */
    void setValue (XMValue v);
    /** Set value with Data parameter
        @param v Data value
     */
    void setValue (Data v);

    /** Implementation of clear() */
    void clear ();
    /** Implementation of append(XMValue) following type checks
        @param v X-Midas data value
     */
    void add (XMValue v);
    /** Implementation of append(String,XMValue) following type checks
        @param k key
        @param v X-Midas data value
     */
    void add (String k, XMValue v);
    /** Implementation of set(String,XMValue) following type checks
        @param k key
        @param v X-Midas data value
     */
    void put (String k, XMValue v);
    /** Implementation of set(int,XMValue) following type checks
        @param i index
        @param v X-Midas value
     */
    void set (int i, XMValue v);
    /** Implementation of set(int,XMValue) for a KVLIST
        @param i index
        @param k key
        @param v X-Midas value
     */
    void set (int i, String k, XMValue v);
    /** Implementation of setValue(char) following type checks
        @param t type
     */
    void setValue (char t);
    /** Implementation of setValue(char,int,double) following type checks
        @param t type
        @param s size
        @param v value
     */
    void setValue (char t, int s, double v);
    /** Implementation of getFormatType()
        @return type
     */
    char getFormatType ();
    /** Implementation of getSize()
        @return size
     */
    int size ();
    /** Indicates if scalar number (undefined if not a numeric type)
        @return if scalar number
     */
    boolean isScalar ();
    /** Implementation of containsKey(String) following type checks
        @param key key to be checked
        @return whether or not the key is there
     */
    boolean containsKey (String key);
    /** Gets the pair at index i in a KVLIST
        @param i index
        @return key-value pair
     */
    KVPair getPair (int i);
    /** Implementation of get(int) following type checks
        @param i index
        @return X-Midas value
     */
    XMValue get (int i);
    /** Implementation of get(String) following type checks
        @param key key
        @return X-Midas value
     */
    XMValue get (String key);
    /** Implementation of vectorize() following type checks
        @return X-Midas value
     */
    XMValue vectorize ();
    /** Implementation of tablize() following type checks
        @return X-Midas value
     */
    XMValue tablize ();
    /** Implementation of keys() following type checks
        @return X-Midas value
     */
    XMValue keys ();
    /** Implementation of items() following type checks
        @return X-Midas value
     */
    XMValue items ();
    /** Implementation of toD() following type checks
        @return conversion to a double value
     */
    double toD ();
    /** Implementation of toF() following type checks
        @return conversion to a float value
     */
    float toF ();
    /** Implementation of toX() following type checks
        @return conversion to a 64-bit integer value
     */
    long toX ();
    /** Implementation of toL() following type checks
        @return conversion to a 32-bit integer value
     */
    int toL ();
    /** Implementation of toI() following type checks
        @return conversion to a 16-bit integer value
     */
    short toI ();
    /** Implementation of toB() following type checks
        @return conversion to an 8-bit integer value
     */
    byte toB ();
    /** Implementation of toA() following type checks
        @return conversion to an Ascii string value
     */
    String toA ();
    /** Implementation of toZ() following type checks
        @return conversion to a string value if data exists
     */
    String toZ ();
    /** Handles free of native pointers in applicable classes */
    default void delete () {};
  }
}
