Package com.ebay.erl.mobius.core.model

Source Code of com.ebay.erl.mobius.core.model.Tuple

package com.ebay.erl.mobius.core.model;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configurable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.DataInputBuffer;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.RawComparator;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;

import com.ebay.erl.mobius.core.ConfigureConstants;
import com.ebay.erl.mobius.core.collection.CaseInsensitiveTreeMap;
import com.ebay.erl.mobius.util.Util;

/**
* Represents a record(row) in a dataset.
* <p>
*
* <p>
* This product is licensed under the Apache License,  Version 2.0,
* available at http://www.apache.org/licenses/LICENSE-2.0.
*
* This product contains portions derived from Apache hadoop which is
* licensed under the Apache License, Version 2.0, available at
* http://hadoop.apache.org.
*
* © 2007 – 2012 eBay Inc., Evan Chiu, Woody Zhou, Neel Sundaresan
*
*/
public class Tuple
  implements WritableComparable<Tuple>, Cloneable, Configurable, RawComparator<Tuple>
  public static void main(String[] arg)
    throws Throwable
  {
    DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File("s:/test.binary")));
    //dos.writeUTF("00_DW_LSTG_ITEM");
    dos.writeInt(5);
    dos.flush();
    dos.close();
  }
 
 
  protected long estimate_size_in_bytes = 8/* object header*/;
 
 
  public static final byte BYTE_TYPE      = 0;
 
  public static final byte SHORT_TYPE      = 1;
 
  public static final byte INTEGER_TYPE    = 2;
 
  public static final byte LONG_TYPE      = 3;
 
  public static final byte FLOAT_TYPE      = 4
 
  public static final byte DOUBLE_TYPE    = 5;
 
  public static final byte STRING_TYPE    = 6;
 
  /**
   * java.sql.Date, only contains yyyy-mm-dd
   */
  public static final byte DATE_TYPE      = 7;
 
  /**
   * java.sql.Timestamp type, contains
   * yyyy-mm-dd hh:mm:ss.[fff]
   */
  public static final byte TIMESTAMP_TYPE    = 8;
 
  /**
   * java.sql.Time type, contains hh:mm:ss.[fff]
   */
  public static final byte TIME_TYPE      = 9;
 
 
  public static final byte BOOLEAN_TYPE    = 10;
 
 
  /**
   * array of elements, the element types have
   * to be the one supported by Tuple.
   */
  public static final byte ARRAY_TYPE      = 11;
 
 
  /**
   * representing value generated by a combiner
   */
  public static final byte RESULT_WRAPPER_TYPE    = 120;
 
 
  /**
   * represents org.apache.hadoop.io.NullWritable
   */
  public static final byte NULL_WRITABLE_TYPE = 121;
 
  /**
   * Represents com.ebay.erl.mobius.core.model.Tuple type.
   */
  public static final byte TUPLE_TYPE      = 122;
 
  /**
   * Represents byte[].
   */
  public static final byte BYTE_ARRAY_TYPE  = 123;
 
  /**
   * Represents {@link CaseInsensitiveTreeMap} type.
   */
  public static final byte STRING_MAP_TYPE   = 124;
 
  /**
   * Represents {@link org.hadoop.hadoop.io.Writable}
   * type.
   */
  public static final byte WRITABLE_TYPE    = 125;
 
  /**
   * Represents {@link java.io.Serializable}
   */
  public static final byte SERIALIZABLE_TYPE  = 126;
 
  /**
   * Represents java <code>null</code>.
   */
  public static final byte NULL_TYPE      = 127;
 
  private static final Log LOGGER = LogFactory.getLog(Tuple.class);
   
  /**
   * a mapping from a column name to an index in the {@link #values}
   * array to get it value.
   */
  protected Map<String, Integer> namesToIdxMapping = new HashMap<String, Integer>();
 
 
  /**
   * to hold the actual values of columns within this mapper.
   */
  protected List<Object> values      = new ArrayList<Object>(1)
 
  /**
   * key used for synchronizing update activities to
   * this tuple.
   */
  protected final String _INSERT_KEY  = "insert";
 
  /**
   * The default delimiter to separate the column
   * values, used in {@link #toString()} and can
   * be override by providing {@link ConfigureConstants.TUPLE_TO_STRING_DELIMITER}
   */
  private static String _DELIMITER  = "\t";
 
  /**
   * Hadoop configuration object.
   */
  protected Configuration conf;
 
  protected boolean isMutable = true;
 
 
 
  /**
   * For quick look up of lower cases form of a given string so
   * we don't to toLowerCase() everytime when retriving or setting
   * value to a tuple
   */
  protected static Map<String/* any string*/, String/*lower case of the key*/> lowerCases =
    Collections.synchronizedMap(new HashMap<String, String>());
 
 
  protected static Map<Set<String>/*not sorted set*/, List<String>/*sorted keys*/> sortedKeys =
    Collections.synchronizedMap(new HashMap<Set<String>, List<String>>());
 
  /**
   * An immutable {@link Tuple} which contains only single Column
   * NULL with null value.
   */
  public static final Tuple NULL;
 
  static
  {
    Tuple nullTuple  = new Tuple();
    nullTuple.putNull("NULL");
    NULL = Tuple.immutable(nullTuple);
  }
 
 
  protected String[] toStringOrdering;
 
 
 
  protected synchronized static String lowerCase(String key)
  {
    String result = lowerCases.get(key);
    if( result==null ){
      result = key.toLowerCase();
      lowerCases.put(key, result);
    }
    return result;
  }
 
  protected synchronized static List<String> getSorted(Set<String> aKeySet)
  {
    List<String> sorted = sortedKeys.get(aKeySet);
    if( sorted==null ){
      sorted = new ArrayList<String>();
      for( String aKey:aKeySet ){
        sorted.add(lowerCase(aKey));
      }
      Collections.sort(sorted);
      sortedKeys.put(aKeySet, sorted);
    }
    return sorted;
  }
 
   
  /**
   * To set the schema of this tuple.
   * <p>
   *
   * This method is called when deserialize
   * a tuple from disk, and should be called
   * only in that case.
   * <p>
  
   * The ordering of the <code>schema</code> is
   * sorted first then set into this tuple.  It
   * it because when a tuple is being serialize to
   * disk, the values are extracted according to
   * the order of their name.  So when deserialize
   * it back, the schema need to be sorted first
   * and then set.
   * <p>
   *
   * The reason of doing this is because Mobius stores
   * the schema in hadoop configuration, but the user
   * might create Tuples with the same schema but in
   * different ordering (insert the values in the different
   * order than the defined schema).  To solve this
   * problem, Mobius always serialize the values in
   * a tuple according to their schema name order, so it
   * can be deserialized back always in the right schema.
   * 
   */
  public void setSchema(String[] schema)
  { 
    Arrays.sort(schema);
   
    this.namesToIdxMapping.clear();
    int idx = 0;
    for( String aName:schema )
    {
      this.namesToIdxMapping.put(lowerCase(aName), idx++);
    }
  }
 
  /**
   * Check if the given <code>name</code> exists
   * in this tuple or not, if so, returns its index.
   * 
   * @return if the given <code>name</code> is in the
   * schema of this tuple, the index of the schema
   * is returned.  Otherwise, {@link IllegalArgumentException}
   * is thrown.
   */
  protected int check_in_schema(String name)
  { 
    Integer idx = null;
    if( (idx=this.namesToIdxMapping.get(lowerCase(name)))!=null )
    {
      return idx;
    }
    else
    {
      throw new IllegalArgumentException("["+name+"] doesn't exist in this tuple's schema:"+this.namesToIdxMapping.keySet()+", index:"+this.namesToIdxMapping.values());
    }   
  }
 
  /**
   * Get the value of the given column <code>name</code> in
   * the <code>expecting_type</code>.
   * <p>
   *
   * If the original <code>value</code> is not in the exact
   * same <code>expecting_type</code>, Mobius will try to
   * convert it to the <code>expecting_type</code> and return
   * it.
   * <p>
   *
   * If the original <code>value</code> is null, then
   * <code>default_value</code> is returned.
   *
   * @param expecting_type user specified type for the returned value.
   * @param name the name of a column within this tuple.
   * @param value the original value of the column <code>name</code>
   * @param default_value if the original value is null, then <code>default_value</code>
   * is returned.
   * @return
   */
  protected Object get(byte expecting_type, String name, Object value, Object default_value)
  {
    byte actual_type  = Tuple.getType(value);
   
    if( expecting_type==Tuple.getType(value) )
    {
      return value;
    }
    else
    {
      // expecting type and actual type are different.     
      if ( Tuple.isNumericalType(expecting_type) && Tuple.isNumericalType(actual_type) )
      {
        if( value==null )
        {
          return default_value;
        }
       
        // expecting value and actual value are both numerical type,
        // but not exact the same, perform transformation.
        switch(expecting_type)
        {
          case BYTE_TYPE:
            return ((Number)value).byteValue();
          case SHORT_TYPE:
            return ((Number)value).shortValue();
          case INTEGER_TYPE:
            return ((Number)value).intValue();
          case LONG_TYPE:
            return ((Number)value).longValue();
          case FLOAT_TYPE:
            return ((Number)value).floatValue();
          case DOUBLE_TYPE:
            return ((Number)value).doubleValue();
          default:
            throw new IllegalArgumentException(String.format("%02X", expecting_type)+" is not numerical type.");
        }
      }
      else if(expecting_type==STRING_TYPE && actual_type!=STRING_TYPE)
      {
        if( value==null )
        {
          return default_value;
        }
       
        LOGGER.trace("Accessing column["+name+"], the expecting type is ["+Tuple.getTypeString(expecting_type)+"], " +
            "but actual type is ["+Tuple.getTypeString(actual_type)+"], using toString() to get the value.");
        // expecting type is string, but the actual type is not string,
        // convert it to string by calling toString().
        return value.toString();
      }
      else if( Tuple.isDateType(expecting_type) && Tuple.isDateType(actual_type) )
      {
        // date type, but the expecting type is not the same as the actual type.
        // Ex:, expecting java.sql.Date, but actual is java.sql.Timestamp
        if( value==null )
        {
          return default_value;
        }
       
       
        // use java.util.Date as the actual type would be
        // either java.sql.Date, java.sql.Time or
        // java.sql.Timestamp.
        java.util.Date actual_value = (java.util.Date)value;
       
        switch(expecting_type)
        {
          case Tuple.DATE_TYPE:
            java.sql.Date sqlDate = new java.sql.Date(actual_value.getTime());
            return sqlDate;
          case Tuple.TIME_TYPE:
            java.sql.Time sqlTime = new java.sql.Time(actual_value.getTime());
            return sqlTime;
          case Tuple.TIMESTAMP_TYPE:
            java.sql.Timestamp sqlTimeStamp = new java.sql.Timestamp(actual_value.getTime());
            return sqlTimeStamp;
          default:
            throw new IllegalArgumentException(Tuple.getTypeString(actual_type)+" is not a date type.");
        }
       
      }
      else if( Tuple.isDateType(expecting_type) && actual_type==STRING_TYPE )
      {
        // expecting type is date type, but the actual type is string
        switch(expecting_type)
        {
          case Tuple.DATE_TYPE:
            java.sql.Date sqlDate = java.sql.Date.valueOf((String)value);
            return sqlDate;
          case Tuple.TIME_TYPE:
            java.sql.Time sqlTime = java.sql.Time.valueOf((String)value);
            return sqlTime;
          case Tuple.TIMESTAMP_TYPE:
            java.sql.Timestamp sqlTimeStamp = java.sql.Timestamp.valueOf((String)value);
            return sqlTimeStamp;
          default:
            throw new IllegalArgumentException(Tuple.getTypeString(actual_type)+" is not a date type.");
        }
      }
      else if( Tuple.isNumericalType(expecting_type) && actual_type==STRING_TYPE )
      {       
        if( value==null )
        {
          return default_value;
        }
       
        // expecting type is numerical, but the actual type is string,
        // try to convert it into numerical value
        String value_str = (String)value;
        try
       
          switch(expecting_type)
          {
            case BYTE_TYPE:
              return Byte.parseByte(value_str);
            case SHORT_TYPE:
              return Short.parseShort(value_str);
            case INTEGER_TYPE:
              return Integer.parseInt(value_str);
            case LONG_TYPE:
              return Long.parseLong(value_str);
            case FLOAT_TYPE:
              return Float.parseFloat(value_str);
            case DOUBLE_TYPE:
              return Double.parseDouble(value_str);
            default:
              throw new IllegalArgumentException(String.format("%02X", expecting_type)+" is not numerical type.");
          }
        }catch(NumberFormatException e)
        {
          throw new NumberFormatException("The value of column["+name+"] is ["+value_str+"] and cannot be converted into "+Tuple.getTypeString(expecting_type));
        }
      }
      else if( expecting_type==BOOLEAN_TYPE && actual_type==STRING_TYPE )
      {
        return Boolean.valueOf((String)value);
      }
      throw new ClassCastException("Column ["+name+"] is " +
          Tuple.getTypeString(actual_type) + ", cannot be converted into "+Tuple.getTypeString(expecting_type));
   
  }
 
  /**
   * Get the value for column <code>name</code> in the
   * <code>expecting_type</code>.
   *
   * @param expecting_type the type of the returned object.
   * @param name name of a column in this tuple.
   * @param default_value if the value of the column is null, then
   * <code>default_value</code> is returned.
   * @return the value in the <code>expected_type</code> of column
   * <code>name</code>
   */
  protected Object get(byte expecting_type, String name, Object default_value)
  { 
    Object value = this.get(name);
    return this.get(expecting_type, name, value, default_value);
  }
 
  /**
   * Get the value of <code>idx</code><sub>th</sub>
   * column in this tuple.
   * <p>
   *
   * If the value of that column is not double, Mobius will
   * try to convert it to double if possible, otherwise,
   * {@link NumberFormatException} will be thrown.
   *
   * @param idx index to a column in this tuple, starts from 0.
   * @param default_value if the value of the column is null,
   * then <code>default_value</code> is returned.
   * @return
   */
  public Double getDouble(int idx, double default_value)
  {
    Object value = this.get(idx);
    return (Double)this.get(Tuple.DOUBLE_TYPE, "@index:"+idx, value, default_value);
 
 
  /**
   * Get the readable string representation for a given
   * type.
   *
   * @param type type supported by Tuple.
   * @return a readable string representation of the
   * <code>type</code>.
   */
  public static String getTypeString(byte type)
  {
    TupleTypeHandler<String> converter = new TupleTypeHandler<String>(){

      @Override
      protected String on_boolean() throws IOException {
        return Boolean.class.getCanonicalName();
      }

      @Override
      protected String on_byte() throws IOException {
        return Byte.class.getCanonicalName();
      }

      @Override
      protected String on_byte_array() throws IOException {       
        return "byte[]";
      }

      @Override
      protected String on_date() throws IOException {
        return java.sql.Date.class.getCanonicalName();
      }

      @Override
      protected String on_default() throws IOException {
        throw new IllegalArgumentException("Unsupported type ["+String.format("0x%02X", type)+"]");
      }

      @Override
      protected String on_double() throws IOException {
        return Double.class.getCanonicalName();
      }

      @Override
      protected String on_float() throws IOException {
        return Float.class.getCanonicalName();
      }

      @Override
      protected String on_integer() throws IOException {
        return Integer.class.getCanonicalName();
      }

      @Override
      protected String on_long() throws IOException {
        return Long.class.getCanonicalName();
      }

      @Override
      protected String on_null() throws IOException {
        return "Null";
      }

      @Override
      protected String on_null_writable() throws IOException {
        return NullWritable.class.getCanonicalName();
      }

      @Override
      protected String on_serializable() throws IOException {
        return Serializable.class.getCanonicalName();
      }

      @Override
      protected String on_short() throws IOException {
        return Short.class.getCanonicalName();
      }

      @Override
      protected String on_string() throws IOException {
        return String.class.getCanonicalName();
      }

      @Override
      protected String on_string_map() throws IOException {
        return Map.class.getCanonicalName()+"<String, String>";
      }

      @Override
      protected String on_time() throws IOException {
        return Time.class.getCanonicalName();
      }

      @Override
      protected String on_timestamp() throws IOException {
        return Timestamp.class.getCanonicalName();
      }

      @Override
      protected String on_tuple() throws IOException {
        return Tuple.class.getCanonicalName();
      }

      @Override
      protected String on_writable() throws IOException {
        return WritableComparable.class.getCanonicalName();
      }

      @Override
      protected String on_result_wrapper() throws IOException {
        return ResultWrapper.class.getCanonicalName();
      }
     
      @Override
      protected String on_array() throws IOException {
        return Array.class.getCanonicalName();
      }
    };
   
    try {
      return converter.handle(type);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
 
 
  /**
   * Get the type of the given <code>obj</code>
   * in <code>byte</code> format, one of the
   * supported type in Tuple.
   */
  @SuppressWarnings("unchecked")
  public static byte getType(Object obj)
  {
    if (obj ==null )
    {
      return NULL_TYPE;
    }
    else if (obj instanceof Byte)
    {
      return BYTE_TYPE;
    }
    else if( obj instanceof Short)
    {
      return SHORT_TYPE;
    }
    else if( obj instanceof Integer )
    {
      return INTEGER_TYPE;
    }
    else if (obj instanceof Long)
    {
      return LONG_TYPE;
    }
    else if (obj instanceof Float)
    {
      return FLOAT_TYPE;
    }
    else if (obj instanceof Double)
    {
      return DOUBLE_TYPE;
    }
    else if (obj instanceof String)
    {
      return STRING_TYPE;
    }
    else if (obj instanceof java.sql.Date)
    {
      return DATE_TYPE;
    }
    else if (obj instanceof Timestamp)
    {
      return TIMESTAMP_TYPE;
    }
    else if (obj instanceof Time)
    {
      return TIME_TYPE;
    }   
    else if (obj instanceof Boolean)
    {
      return BOOLEAN_TYPE;
    }
    else if (obj instanceof Map)
    {
      return STRING_MAP_TYPE;
    }
    else if (obj instanceof Array)
    {
      return ARRAY_TYPE;
    }
    else if (obj instanceof NullWritable)
    {
      return NULL_WRITABLE_TYPE;
    }
    else if (obj instanceof Tuple )   
    {
      return TUPLE_TYPE;
    }
    else if (obj instanceof ResultWrapper)
    {
      return RESULT_WRAPPER_TYPE;
    }
     
    else if (obj instanceof Writable)
    {
      return WRITABLE_TYPE;
    }
    else if (obj instanceof Serializable)
    {
      return SERIALIZABLE_TYPE;
    }
    else if ( obj instanceof byte[])
    {
      return BYTE_ARRAY_TYPE;
    }
    else
    {
      throw new IllegalArgumentException(obj.getClass().getName()+" is not supported in Tuple.");
    }
  }

  /**
   * Deserialize the tuple from the input
   * <code>in</code>.
   */
  @Override
  public void readFields(DataInput in)
    throws IOException
 
    if( this.values==null )
    {
      this.values = new ArrayList<Object>();
    }
    else
      this.values.clear();
   
    int columns_nbrs = in.readInt();
   
    ReadFieldImpl read_impl = new ReadFieldImpl(this.values, in, this.conf);
   
    for( int i=0;i<columns_nbrs;i++ )
    {
      byte type = in.readByte();
      read_impl.handle(type);
    }
  }

  /**
   * Serialize this tuple to the output <code>out</code>.
   * <p>
   *
   * When serialize, the values are stored in the order
   * of schema name's ordering. See {@link #setSchema(String[])}
   * for more explanation.
   */
  @Override
  public void write(DataOutput out)
    throws IOException
 
    // write the size of the column of this tuple
    out.writeInt(this.values.size());
   
    if( this.values.size()!=this.namesToIdxMapping.size() )
    {
      StringBuffer sb = new StringBuffer();
      for( Object v:values)
        sb.append(v.toString()).append(",");
      throw new IllegalArgumentException(this.getClass().getCanonicalName()+", the length of values and schmea is not the same, " +
          "very likely the schema of this tuple has not been set yet, please set it using Tuple#setSchema(String[])." +
          " Values:["+sb.toString()+"] schema:"+this.namesToIdxMapping.keySet());
    }
   
    WriteImpl writeImpl = new WriteImpl(out);
   
    for ( String aColumnName : getSorted(this.namesToIdxMapping.keySet()) )
    {       
      Object value = this.values.get(this.namesToIdxMapping.get(aColumnName));
      byte type = getType(value);
      out.write(type);
     
      writeImpl.setValue(value);
      writeImpl.handle(type);
    }
  }

  /**
   * Compare this tuple with <code>other</code>.
   * <p>
   *
   * It calls {@link #compare(Tuple, Tuple)} underline.
   */
  @Override
  public int compareTo(Tuple other)
  { 
    return compare(this, other);
  }
 
 
  /**
   * Add a new column in the given <code></code> with
   * provided <code>value</code>.
   *
   *  @throws UnsupportedOperationException if this tuple is immutable.
   */
  public Tuple insert(String name, Object value)
  { 
    if( !this.isMutable )
    {
      throw new UnsupportedOperationException("This tuple is immutable, cannot be modified.");
    }
   
    TupleColumnName tcn = TupleColumnName.valueOf(lowerCase(name));
   
    String id    = tcn.getID();
    String mapKey  = tcn.getMapKey();
   
    synchronized(this._INSERT_KEY)
    {     
      if( this.namesToIdxMapping.containsKey(id) )
      {
        // do nothing
      }
      else
      {
        this.namesToIdxMapping.put(id, this.namesToIdxMapping.size());
      }
     
      int value_idx = this.namesToIdxMapping.get(id);
     
      if( value_idx<this.values.size() )
      {     
        // replace mode, replace the old value
        if( mapKey==null )
        {
          // the <code>name</code> is not map ID style
          this.values.set(value_idx, value);
        }
        else
        {
          if (this.values.get(value_idx) instanceof CaseInsensitiveTreeMap)
          {
            ((CaseInsensitiveTreeMap)this.values.get(value_idx)).put(mapKey, value.toString());
          }
          else
          {
            throw new IllegalArgumentException("Column ["+id+"] is not "+CaseInsensitiveTreeMap.class.getCanonicalName()+", " +
                "cannot change the value using map style ID ["+name+"]");
          }
        }
      }
      else if (value_idx == this.values.size())
      {
        // insert mode
        if( mapKey==null )
        {
          // the <code>name</code> is not map ID style
          this.values.add(value_idx, value);
        }
        else
        {
          // user tries to use a Map style ID to add new value, disallow
          throw new IllegalArgumentException("Column ["+id+"] has not been initialized as Map, " +
              "cannot use ["+name+"] to change the value of the key directly.");
        }
      }
      else
      {
        throw new IllegalStateException();
      }
    }
   
    return this;
  }

  /**
   * Get Hadoop configuration.
   */
  @Override
  public Configuration getConf()
  {
    return this.conf;
  }

  /**
   * Set the Hadoop configuration, the
   * delimiter to be used to separated
   * the column value in the {@link #toString()}
   * is also set here.
   * <p>
   *
   * The default delimiter is tab, unless
   * {@link ConfigureConstants.TUPLE_TO_STRING_DELIMITER}
   * is set by user.
   */
  @Override
  public void setConf(Configuration conf)
  {
    this.conf = conf;
    synchronized(Tuple._DELIMITER)
    {
      if ( Tuple._DELIMITER.isEmpty() )
      {
        Tuple._DELIMITER = this.conf.get(ConfigureConstants.TUPLE_TO_STRING_DELIMITER, "\t");
      }
    }
  }

  private final TupleColumnComparator _COLUMN_COMPARATOR = new TupleColumnComparator();
 
  /**
   * compare two tuples in low level row format.
   */
  @Override
  public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2)
  {
    DataInputBuffer d1 = new DataInputBuffer();
    d1.reset(b1, s1, l1);
   
    DataInputBuffer d2 = new DataInputBuffer();
    d2.reset(b2, s2, l2);
   
    int _compare_result = Integer.MAX_VALUE;
   
    try
    {
      // read number of columns from the two tuple
      int columns_nbr1 = d1.readInt();
      int columns_nbr2 = d2.readInt();
     
      int upper_bound = Math.min(columns_nbr1, columns_nbr2);
     
      // same column size, start to compare column by column
     
      for( int i=0;i<upper_bound;i++ )
      {
        byte type1 = d1.readByte();
        byte type2 = d2.readByte();
       
        _COLUMN_COMPARATOR.setType(type1, type2);
        _compare_result = _COLUMN_COMPARATOR.compare(d1, d2, this.conf);
       
        // comparing for a column has complete       
        if ( _compare_result!=0 && _compare_result!=Integer.MAX_VALUE  )
        {
          // has different, return
          return _compare_result;
        }
      }// end of iterating columns until the upper limit
     
      // finished all columns comparison(up to the upper-bound), still cannot find difference,
      // use the column size as the comparing result.
     
      _compare_result = columns_nbr1 - columns_nbr2;
    }
    catch(IOException e)
    {
      throw new RuntimeException(e);
    }
   
    if (_compare_result==Integer.MAX_VALUE )
      throw new IllegalArgumentException();
   
    return _compare_result;
  }

  /**
   * Comparing two tuples.
   * <p>
   *
   * It compares the values of the two tuples one
   * by one in sequence, and as long as there is a
   * difference between two values, then the
   * difference is returned.
   * <p>
   *
   * If the number of values in the tuples are
   * different, the values are compared up to
   * the boundary of the smaller size tuple.  If
   * all the values before the boundary have no
   * differences, then the smaller size tuple
   * is considered to be placed before the bigger
   * size tuple.
   */
  @Override
  public int compare(Tuple t1, Tuple t2)
  { 
    int value1_nbr = t1.values.size();
    int value2_nbr = t2.values.size();
   
    int upper_bound = Math.min(value1_nbr, value2_nbr);
   
    int _compare_result = Integer.MAX_VALUE;
   
    try
    {
      for( int i=0;i<upper_bound;i++)
      {
        Object v1 = t1.values.get(i);
        Object v2 = t2.values.get(i);
       
        byte type1 = Tuple.getType(v1);
        byte type2 = Tuple.getType(v2);
       
        _COLUMN_COMPARATOR.setType(type1, type2);
        _compare_result = _COLUMN_COMPARATOR.compare(v1, v2, null);
       
        // comparing for a column has complete       
        if ( _compare_result!=0 && _compare_result!=Integer.MAX_VALUE  )
        {
          // has different, return
          return _compare_result;
        }
      }// end of iterating columns until the upper limit
     
      // finished all columns comparison(up to the upper-bound), still cannot find difference,
      // use the column size as the comparing result.
     
      _compare_result = value1_nbr - value2_nbr;
    }
    catch(IOException e)
    {
      throw new RuntimeException(e);
    }
    if (_compare_result==Integer.MAX_VALUE )
      throw new IllegalArgumentException();
   
    return _compare_result; // all the same, return 0
  }
 
 
  public Tuple put(String name, ResultWrapper<?> v)
  {
    if( v==null )
      throw new NullPointerException("value cannot be null.");
   
    this.insert(name, v);
    return this;
  }
 
 
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to <code>null</code>.
   *
   * @return return this tuple
   */
  public Tuple putNull(String name)
  {
    this.insert(name, null);
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, byte value)
  {
    this.insert(name, value);
   
    // add 16 bytes, 16 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // single Byte (as the value will be auto-boxed
    // into Byte) on a 64bit VM.
    this.estimate_size_in_bytes += 16;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, byte[] value)
  {
    this.insert(name, value);
   
    // add the length of the value plus 16 bytes
    // base.
    this.estimate_size_in_bytes += (value.length + 16);
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, short value)
  {
    this.insert(name, value);
   
    // add 16 bytes, 16 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // single Short (as the value will be auto-boxed
    // into Short) on a 64bit VM.
    this.estimate_size_in_bytes += 16;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, int value
  {
    this.insert(name, value);
   
    // add 16 bytes, 16 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // Integer.MAX_VALUE (as the value will be auto-boxed
    // into Integer) on a 64bit VM.
    this.estimate_size_in_bytes += 16;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, long value
  {
    this.insert(name, value);
   
    // add 24 bytes, 24 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // Long.MAX_VALUE (as the value will be auto-boxed
    // into Long) on a 64bit VM.
    this.estimate_size_in_bytes += 24;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, float value
  {
    this.insert(name, value);
   
    // add 16 bytes, 16 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // Float.MAX_VALUE (as the value will be auto-boxed
    // into Float) on a 64bit VM.
    this.estimate_size_in_bytes += 16;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, double value)
  {
    this.insert(name, value);
   
    // add 24 bytes, 24 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // Double.MAX_VALUE (as the value will be auto-boxed
    // into Double) on a 64bit VM.
    this.estimate_size_in_bytes += 24;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, boolean value)
  {   
    this.insert(name, value);
   
    // add 16 bytes for inserting a boolean,
    // it will be auto-box into Boolean, and
    // using java.lang.instrument.Instrumentation
    // to test single Boolean on a 64bit VM require
    // 16 bytes.
    this.estimate_size_in_bytes += 16;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, java.sql.Date value)
  {
    if( value==null )
      throw new NullPointerException("value cannot be null.");
   
    this.insert(name, value);
   
    // add 24 bytes, 24 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // single java.sql.Date on a 64bit VM.
    this.estimate_size_in_bytes += 24;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, Timestamp value)
  {
    if( value==null )
      throw new NullPointerException("value cannot be null.");
   
    this.insert(name, value);
   
    // add 32 bytes, 32 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // single java.sql.Timestamp on a 64bit VM.
    this.estimate_size_in_bytes += 32;
   
    return this;
 
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, Time value)
  {
    if( value==null )
      throw new NullPointerException("value cannot be null.");
   
    this.insert(name, value);
   
    // add 24 bytes, 24 bytes is derived from
    // java.lang.instrument.Instrumentation to test
    // single java.sql.Time on a 64bit VM.
    this.estimate_size_in_bytes += 24;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, String value)
  {
    if( value==null )
      throw new NullPointerException("value cannot be null.");
   
    this.insert(name, value);
   
    // reference: http://www.javamex.com/tutorials/memory/string_memory_usage.shtml
    // Minimum String memory usage (bytes) = 8 * (int) ((((no chars) * 2) + 45) / 8)
    this.estimate_size_in_bytes += 8 * (int) ((((value.length()) * 2) + 45) / 8);
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, CaseInsensitiveTreeMap value)
  {
    // insure the value is case-insensitive TreeMap
    this.insert(name, value);

    // reference: http://www.javamex.com/tutorials/memory/string_memory_usage.shtml
    // Minimum String memory usage (bytes) = 8 * (int) ((((no chars) * 2) + 45) / 8)
    long est_key_size = 8 * (int) ((((64/*assume 64 chars string*/) * 2) + 45) / 8);
    this.estimate_size_in_bytes += 48/*map overhead*/ + est_key_size*2/*assume key and value is about the same size*/;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   */
  public Tuple put(String name, Writable value)
  {
    if( value==null )
      throw new NullPointerException("value cannot be null.");
   
    this.insert(name, value);
   
    // estimate only, put 512 byte here
    this.estimate_size_in_bytes += 512;
   
    return this;
  }
 
  /**
   * For the given column named <code>name</code>,
   * add (if it doesn't exist) to this tuple or update (if it
   * exists) its value to the given <code>value</code>.
   *
   * @return return this tuple
   * @throws IllegalArgumentException if <code>value</code> is instance
   * of Map but not {@link CaseInsensitiveTreeMap}.  Or when <code>value</code>
   * doesn't implement {@link Comparable}
   */
  public Tuple put(String name, Serializable value)
  {
    if( value==null )
      throw new NullPointerException("value cannot be null.");
   
    if( value instanceof Map<?, ?> && !(value instanceof CaseInsensitiveTreeMap) )
    {
      throw new IllegalArgumentException("The supported map type is only "+CaseInsensitiveTreeMap.class.getCanonicalName());
    }
   
    if( value instanceof Comparable<?>)
    {
      this.insert(name, value);
     
      // estimate only, put 512 byte here
      this.estimate_size_in_bytes += 512;
     
      return this;
    }
    else
    {
      throw new IllegalArgumentException(value.getClass().getCanonicalName() +
          " doesn't implement "+Comparable.class.getCanonicalName());
    }
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, -1 is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a short type, Mobius will try to convert it
   * to short, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Short getShort(String name)
  {
    return this.getShort(name, (short)-1);
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a short type, Mobius will try to convert it
   * to short, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Short getShort(String name, short default_value)
  {
    return (Short)get(SHORT_TYPE, name, default_value);
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, -1 is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a short type, Mobius will try to convert it
   * to short, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Integer getInt(String name)
  {
    return getInt(name, -1);
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not an integer type, Mobius will try to convert it
   * to integer, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Integer getInt(String name, int default_value)
  {
    return (Integer)get(INTEGER_TYPE, name, default_value);
  }
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, -1L is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a long type, Mobius will try to convert it
   * to long, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Long getLong(String name)
  {
    return (Long)get(LONG_TYPE, name, -1L);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a long type, Mobius will try to convert it
   * to long, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Long getLong(String name, long default_value)
  {
    return (Long)get(LONG_TYPE, name, default_value);
  }
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, -1F is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a float type, Mobius will try to convert it
   * to float, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Float getFloat(String name)
  {
    return (Float)get(FLOAT_TYPE, name, -1F);
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a float type, Mobius will try to convert it
   * to float, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Float getFloat(String name, float default_value)
  {
    return (Float)get(FLOAT_TYPE, name, default_value);
  }
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, -1D is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a double type, Mobius will try to convert it
   * to double, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Double getDouble(String name)
  {
    return (Double)get(DOUBLE_TYPE, name, -1D);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a double type, Mobius will try to convert it
   * to double, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Double getDouble(String name, double default_value)
  {
    return (Double)get(DOUBLE_TYPE, name, default_value);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, 0x00 is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a byte type, Mobius will try to convert it
   * to byte, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Byte getByte(String name)
  {
    return (Byte)get(BYTE_TYPE, name, 0x00);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a byte type, Mobius will try to convert it
   * to byte, otherwise, {@link NumberFormatException}
   * is thrown.
   */
  public Byte getByte(String name, byte default_value)
  {
    return (Byte)get(BYTE_TYPE, name, default_value);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>false</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a boolean type, Mobius will try to convert it
   * to using {@link Boolean#valueOf(String)}.
   */
  public Boolean getBoolean(String name)
  {
    return (Boolean)get(BOOLEAN_TYPE, name, false);
 
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a boolean type, Mobius will try to convert it
   * to using {@link Boolean#valueOf(String)}.
   */
  public Boolean getBoolean(String name, boolean default_value)
  {
    return (Boolean)get(BOOLEAN_TYPE, name, default_value);
  }
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>null</code>
   * is still returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a string type, Mobius will try to convert it
   * to using the <code>toString()</code> method.
   */
  public String getString(String name)
  {
    return (String)get(STRING_TYPE, name, null);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a string type, Mobius will try to convert it
   * to using the <code>toString()</code> method.
   */
  public String getString(String name, String default_value)
  {
    return (String)get(STRING_TYPE, name, default_value);
  }
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>null</code>
   * is still returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a map type, {@link IllegalArgumentException}
   * is thrown.
   */
  @SuppressWarnings("unchecked")
  public Map<String, String> getMap(String name)
  {
    return (Map<String, String>)get(STRING_MAP_TYPE, name, null);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>null</code>
   * is still returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a date type, Mobius will try to convert it
   * to using the {@link java.sql.Date#valueOf(String)}
   * method.
   */
  public java.sql.Date getDate(String name)
  {
    return (java.sql.Date)get(DATE_TYPE, name, null);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a date type, Mobius will try to convert it
   * to using the {@link java.sql.Date#valueOf(String)}
   * method.
   */
  public java.sql.Date getDate(String name, java.sql.Date default_value)
  {
    return (java.sql.Date)get(DATE_TYPE, name, default_value);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>null</code>
   * is still returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a date type, Mobius will try to convert it
   * to using the {@link java.sql.Timestamp#valueOf(String)}
   * method.
   */
  public Timestamp getTimestamp(String name)
  {
    return (Timestamp)get(TIMESTAMP_TYPE, name, null);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a date type, Mobius will try to convert it
   * to using the {@link java.sql.Timestamp#valueOf(String)}
   * method.
   */
  public Timestamp getTimestamp(String name, java.sql.Timestamp default_value)
  {
    return (Timestamp)get(TIMESTAMP_TYPE, name, default_value);
  }
 
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>null</code>
   * is still returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a date type, Mobius will try to convert it
   * to using the {@link java.sql.Time#valueOf(String)}
   * method.
   */
  public Time getTime(String name)
  {
    return (Time)get(TIME_TYPE, name, null);
  }
 
  /**
   * Get the value of column named <code>name</code>.
   * <p>
   *
   * If the value is <code>null</code>, <code>default_value</code>
   * is returned.
   * <p>
   *
   * If the value is not <code>null</code>, but it's
   * not a date type, Mobius will try to convert it
   * to using the {@link java.sql.Time#valueOf(String)}
   * method.
   */
  public Time getTime(String name, Time default_value)
  {
    return (Time)get(TIME_TYPE, name, default_value);
  }
 
 
  /**
   * Get value directly using index.
   */
  public Object get(int index)
  {
    return this.values.get(index);
  }
 
 
  /**
   * Get the value of column named <code>name</code>.
   *
   * @param name the name of a column.
   * @return value of the column.
   */
  public Object get(String name)
  {
    TupleColumnName tcn = TupleColumnName.valueOf(lowerCase(name));
    int idx = check_in_schema(tcn.getID());
    if( tcn.getMapKey()==null )
    {
      // not using map style name to access the value
      return this.values.get(idx);
    }
    else
    {
      // user is referencing to a value of a map key.
     
      Object value = this.values.get(idx);
      if( value instanceof CaseInsensitiveTreeMap)
      {
        return ((CaseInsensitiveTreeMap)value).get(tcn.getMapKey());
      }
      else
      {
        throw new IllegalArgumentException("The type of column ["+tcn.getID()+"] is not "+CaseInsensitiveTreeMap.class.getCanonicalName()+" but "+value.getClass().getCanonicalName()+
            ", the given ID ["+name+"] is a map style ID and cannot be applied to this column.");
      }
    }
  }
 

  /**
   * Return a new instance of {@link Tuple} which
   * contains the exact same data of this one.
   */
  @Override
  public Tuple clone()
  {
    Tuple clone = new Tuple();
    clone.namesToIdxMapping = new HashMap<String, Integer>();
    clone.values = new ArrayList<Object>(this.values.size());
   
    // fulfill the values with null
    for( int i=0;i<this.values.size();i++ )
    {
      clone.values.add(null);
    }
   
    for( String columnName:this.namesToIdxMapping.keySet() )
    {
      Integer idx = this.namesToIdxMapping.get(columnName);
     
      clone.namesToIdxMapping.put(columnName, idx);
      clone.values.set(idx, this.get(idx));
    }
   
    return clone;
  }
 
  @Override
  public int hashCode()
  {
    int hashCode = 0;
    for(Object obj:this.values)
    {
      //if( obj==null )
      //  throw new RuntimeException(this.namesToIdxMapping.toString()+":"+this.values.toString());
      if( obj==null )
        continue;
     
      hashCode += obj.hashCode();
    }
    return hashCode;
  }
 
  /**
   * Test if the given <code>type</code> is
   * {@link #BYTE_TYPE}, {@link #SHORT_TYPE},
   * {@link #INTEGER_TYPE}, {@link #LONG_TYPE},
   * {@link #FLOAT_TYPE}, or {@link #DOUBLE_TYPE}.
   * <p>
   *
   * Return <code>true</code> if the <code>type</code>
   * is within the above types, false otherwise.
   */
  public static boolean isNumericalType(byte type)
  {
    return type>=Tuple.BYTE_TYPE && type<=Tuple.DOUBLE_TYPE;
  }
 
  /**
   * Test if the given <code>type</code> is
   * {@link #TIME_TYPE}, {@link #DATE_TYPE}, or
   * {@link #TIMESTAMP_TYPE}.
   * <p>
   *
   * Return <code>true</code> if the <code>type</code>
   * is within the above types, false otherwise.
   */
  public static boolean isDateType(byte type)
  {
    return type==Tuple.DATE_TYPE || type==Tuple.TIMESTAMP_TYPE || type==Tuple.TIME_TYPE;
  }
 
 
  private void setMutable(boolean isMutable)
  {
    this.isMutable = isMutable;
  }
 
  /**
   * return a new instance of tuple that contains the
   * same data of the given <code>t</code> tuple, but
   * reject all modification requests, such as
   * {@link Tuple#insert(String, Object)}.
   * <p>
   *
   * Note that, this method return a new instance, the
   * original <code>t</code> tuple is still a mutable
   * {@linkplain Tuple}.
   */
  public static Tuple immutable(Tuple t)
  { 
    Tuple clone = t.clone();
    clone.setMutable(false);
    return clone;
  }
 
  /**
   * Merge the tuples together, and return a new
   * tuple represents the merged result.
   * <p>
   *
   * All the columns in <code>t1</code> and
   * <code>t2</code> will be put together into
   * the returned Tuple.  If there are columns
   * in <code>t2</code> also appear in <code>t1</code>,
   * then values from <code>t2</code> of those columns
   * will be used instead of the values in <code>t1</code>.
   */
  public static Tuple merge(Tuple t1, Tuple t2)
  {
    Tuple result = new Tuple();
    if( t1!=null )
    {
      for (String aColumn: t1.getSchema() )
      {
        result.insert(aColumn, t1.get(aColumn));
      }
    }
   
    if (t2!=null )
    {
      for( String aColumn:t2.getSchema() )
      {
        result.insert(aColumn, t2.get(aColumn));
      }
    }
   
    return result;
  }
 
  /**
   * Convert this {@link Tuple} into text, the delimiter is specified by
   * "mobius.tuple.tostring.delimiter" (default is tab).
   * <p>
   *
   */
  @Override
  public String toString()
  {
    StringBuffer sb = new StringBuffer();
    if( this.toStringOrdering!=null && this.toStringOrdering.length>0 )
    {
      for (int i=0;i<this.toStringOrdering.length;i++)
      {
        String aColumn = this.toStringOrdering[i];
        Object aValue = this.get(aColumn);
        if( aValue!=null )
          sb.append(aValue.toString());
        if( i<this.values.size()-1 )
          sb.append(Tuple._DELIMITER);
      }
    }
    else
    {
      for( int i=0;i<this.values.size();i++ )
      {
        Object aValue = this.values.get(i);
        if( aValue!=null )
          sb.append(aValue.toString());
        if( i<this.values.size()-1 )
          sb.append(Tuple._DELIMITER);
      }
    }
    return sb.toString();
  }
 
  /**
   * Compare if the <code>obj</code> equals to
   * this tuple or not.
   * <p>
   *
   * Equals only whe the class of this tuple and the
   * <code>obj</code> is the same, both share same
   * schema, and the values of the columns are the same.
   */
  @Override
  public boolean equals(Object obj)
  {
    if( obj==this )
      return true;
   
    if( obj.getClass().equals(this.getClass()) )
    {
      Tuple that = (Tuple)obj;
     
      if( this.namesToIdxMapping.keySet().equals(that.namesToIdxMapping.keySet()))
      {
        // same schema, test the value one by one
        for( String name:this.namesToIdxMapping.keySet() )
        {
          Object v1 = this.get(name);
          Object v2 = that.get(name);
         
          if( v1==null && v2==null ){
            // both null, consider equal, move
            // on to the next
          }
          else if( v1==null && v2!=null ){
            return false;
          }
          else if( v1!=null && v2==null ){
            return false;
          }
          else if( !this.get(name).equals(that.get(name)) )
          {
            // both are not equals
            return false;
          }
        }
        return true;
      }
      else
      {
        return false;
      }
    }
    return false;
  }
 
  /**
   * Return the schema of this column.
   * <p>
   */
  public String[] getSchema()
  {
    try
    {
      String[] schema = new String[this.namesToIdxMapping.size()];
      for( String aColumnName:this.namesToIdxMapping.keySet() )
      {
        int idx = this.namesToIdxMapping.get(aColumnName);// the index of this column
        schema[idx] = aColumnName;
      }
      return schema;
    }catch(NullPointerException e){
      throw e;
    }
  }
 
  /**
   * Convert the <code>source</code> into a tuple.
   * <p>
   *
   * Split the <code>source</code> with the given <code>delimiter</code>,
   * and use them as the values to the returned tuple, then set the
   * schema to the tuple.
   * <p>
   *
   * The ordering of the schema shall be the same as the ordering of the
   * values from the splitted <code>source</code>.
   * <p>
   *
   * If the number of values in the splitted <code>source<code> is greater
   * than the length of <code>schema</code>, <code>IDX_$i</code> is used
   * as the name of those value, where <code>$i</code> starts from the
   * length of <code>schema</code>.
   */
  public static Tuple valueOf(Text source, String[] schema, String delimiter)
  {
   
    Tuple tuple    = new Tuple();
   
    //String[] tokens = source.toString ().split (delimiter, -1);
    List<String> tokens = Util.nonRegexSplit(source.toString(), delimiter);
   
    for( int i=0;i<schema.length;i++)
    {
      if( i<tokens.size() )
      {
        tuple.put (schema[i], tokens.get(i));
      }
      else
      {
        tuple.putNull (schema[i]);
      }
    }
   
    // there are some extra columns that exceed the length of user
    // specified schema, put in the tail.
    for( int i=schema.length;i<tokens.size();i++ )
    {
      tuple.put ("IDX_"+i, tokens.get(i));
    }
   
   
   
    return tuple;
  }
 
  /**
   * Return the estimated size in bytes of this
   * tuple in memory.
   * <p>
   *
   * This calculation is based on 64bit VM.
   */
  public long getEstimatedSizeInMemory()
  {
    return this.estimate_size_in_bytes;
  }
 
 
  public void setToStringOrdering(String[] columns)
  {
    this.toStringOrdering = columns;
  }
 
 
  public boolean hasSchema()
  {
    return this.namesToIdxMapping!=null && this.namesToIdxMapping.keySet().size()>0;
  }
 
 
  /**
   * Represents the name of a tuple column.
   * <p>
   *
   * The column name format is specified as a
   * regular expression in {@link TupleColumnName#COLUMN_NAME_PATTERN}.
   */
  public static final class TupleColumnName
  {
    /**
     * Column name format of a tuple.
     * <p>
     * The format is: <code>([\\p{Graph}&&[^\\.]]+)(\\.([\\p{Graph}&&[^\\.]]+))?</code>
     * <p>
     */
    public static final Pattern COLUMN_NAME_PATTERN = Pattern.compile("([\\p{Graph}&&[^\\.]]+)(\\.([\\p{Graph}&&[^\\.]]+))?");   
   
   
    /**
     * required, means the id of the column name
     */
    private String id;
   
    /**
     * optional, for the Map column type only.  For example,
     * A.B means this tuple has a column named "A" and it's a
     * Map type, and user is trying to access the value of key
     * "B"
     */
    private String mapKey;
   
    private static Map<String, TupleColumnName> tupleColumnNames = new HashMap<String, TupleColumnName>();
   
    /**
     * convert the <code>columnName</code> into a {@link TupleColumnName}.
     */
    public synchronized static TupleColumnName valueOf(String columnName)
    {     
      if( columnName==null || columnName.trim().isEmpty() )
      {
        throw new IllegalArgumentException("column name cannot be null nor empty string.");
      }
     
      TupleColumnName tcn = null;
      if( (tcn=tupleColumnNames.get(columnName))!=null )
      {
        return tcn;
      }
      else{
     
        int dotIdx = columnName.indexOf(".");
       
        if( dotIdx<0 )
        {
          tcn = new TupleColumnName();
          tcn.id = columnName;
          tcn.mapKey = null;
        }
        else if (dotIdx>0)
        {
          if ( dotIdx+1==columnName.length() )
          {
            throw new IllegalArgumentException("Invalid format of Tuple column name:["+columnName+"], please refer the correct format in {@link Tuple#COLUMN_NAME_PATTERN}");
          }
          tcn = new TupleColumnName();
          tcn.id = columnName.substring(0, dotIdx);
          tcn.mapKey = columnName.substring(dotIdx+1);
        }
        else
        {
          // dotIdx==0
          throw new IllegalArgumentException("Invalid format of Tuple column name:["+columnName+"], please refer the correct format in {@link Tuple#COLUMN_NAME_PATTERN}");
        }
        tupleColumnNames.put(columnName, tcn);
        return tcn;
      }
    }
   
    /**
     * Get the column ID.
     */
    public String getID()
    {
      return this.id;
    }
   
    /**
     * If the column type is map, user can use, for
     * example, <code>ID.MAP_KEY</code> to access a column
     * named <code>ID</code>, which is a map, and then use
     * <code>MAP_KEY</code> as the key to get the value.
     */
    public String getMapKey()
    {
      return this.mapKey;
    }
  }
}
TOP

Related Classes of com.ebay.erl.mobius.core.model.Tuple

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.