    001/*
    002 * Copyright (c) 2016-2017 Chris K Wensel <chris@wensel.net>. All Rights Reserved.
    003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
    004 *
    005 * Project and contact information: http://www.cascading.org/
    006 *
    007 * This file is part of the Cascading project.
    008 *
    009 * Licensed under the Apache License, Version 2.0 (the "License");
    010 * you may not use this file except in compliance with the License.
    011 * You may obtain a copy of the License at
    012 *
    013 *     http://www.apache.org/licenses/LICENSE-2.0
    014 *
    015 * Unless required by applicable law or agreed to in writing, software
    016 * distributed under the License is distributed on an "AS IS" BASIS,
    017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    018 * See the License for the specific language governing permissions and
    019 * limitations under the License.
    020 */
    021
    022package cascading.tuple;
    023
    024import java.beans.ConstructorProperties;
    025import java.io.Serializable;
    026import java.lang.reflect.Type;
    027import java.util.ArrayList;
    028import java.util.Arrays;
    029import java.util.Collections;
    030import java.util.Comparator;
    031import java.util.HashMap;
    032import java.util.HashSet;
    033import java.util.Iterator;
    034import java.util.LinkedHashSet;
    035import java.util.LinkedList;
    036import java.util.List;
    037import java.util.Map;
    038import java.util.Set;
    039
    040import cascading.tap.Tap;
    041import cascading.tuple.type.CoercibleType;
    042import cascading.util.Util;
    043
    044/**
    045 * Class Fields represents the field names in a {@link Tuple}. A tuple field may be a literal String value representing a
    046 * name, or it may be a literal Integer value representing a position, where positions start at position 0.
    047 * A Fields instance may also represent a set of field names and positions.
    048 * <p/>
    049 * Fields are used as both declarators and selectors. A declarator declares that a given {@link Tap} or
    050 * {@link cascading.operation.Operation} returns the given field names, for a set of values the size of
    051 * the given Fields instance. A selector is used to select given referenced fields from a Tuple.
    052 * For example; <br/>
    053 * <code>Fields fields = new Fields( "a", "b", "c" );</code><br/>
    054 * This creates a new Fields instance with the field names "a", "b", and "c". This Fields instance can be used as both
    055 * a declarator or a selector, depending on how it's used.
    056 * <p/>
    057 * Or For example; <br/>
    058 * <code>Fields fields = new Fields( 1, 2, -1 );</code><br/>
    059 * This creates a new Fields instance that can only be used as a selector. It would select the second, third, and last
    060 * position from a given Tuple instance, assuming it has at least four positions. Since the original field names for those
    061 * positions will carry over to the new selected Tuple instance, if the original Tuple only had three positions, the third
    062 * and last positions would be the same, and would throw an error on there being duplicate field names in the selected
    063 * Tuple instance.
    064 * <p/>
    065 * Additionally, there are eight predefined Fields sets used for different purposes; {@link #NONE}, {@link #ALL}, {@link #GROUP},
    066 * {@link #VALUES}, {@link #ARGS}, {@link #RESULTS}, {@link #UNKNOWN}, {@link #REPLACE}, and {@link #SWAP}.
    067 * <p/>
    068 * The {@code NONE} Fields set represents no fields.
    069 * <p/>
    070 * The {@code ALL} Fields set is a "wildcard" that represents all the current available fields.
    071 * <p/>
    072 * The {@code GROUP} Fields set represents all the fields used as grouping values in a previous {@link cascading.pipe.Splice}.
    073 * If there is no previous Group in the pipe assembly, the GROUP represents all the current field names.
    074 * <p/>
    075 * The {@code VALUES} Fields set represent all the fields not used as grouping fields in a previous Group.
    076 * <p/>
    077 * The {@code ARGS} Fields set is used to let a given Operation inherit the field names of its argument Tuple. This Fields set
    078 * is a convenience and is typically used when the Pipe output selector is {@code RESULTS} or {@code REPLACE}.
    079 * <p/>
    080 * The {@code RESULTS} Fields set is used to represent the field names of the current Operations return values. This Fields
    081 * set may only be used as an output selector on a Pipe. It effectively replaces in the input Tuple with the Operation result
    082 * Tuple.
    083 * <p/>
    084 * The {@code UNKNOWN} Fields set is used when Fields must be declared, but how many and their names is unknown. This allows
    085 * for arbitrarily length Tuples from an input source or some Operation. Use this Fields set with caution.
    086 * <p/>
    087 * The {@code REPLACE} Fields set is used as an output selector to inline replace values in the incoming Tuple with
    088 * the results of an Operation. This is a convenience Fields set that allows subsequent Operations to 'step' on the
    089 * value with a given field name. The current Operation must always use the exact same field names, or the {@code ARGS}
    090 * Fields set.
    091 * <p/>
    092 * The {@code SWAP} Fields set is used as an output selector to swap out Operation arguments with its results. Neither
    093 * the argument and result field names or size need to be the same. This is useful for when the Operation arguments are
    094 * no longer necessary and the result Fields and values should be appended to the remainder of the input field names
    095 * and Tuple.
    096 */
    097public class Fields implements Comparable, Iterable<Comparable>, Serializable, Comparator<Tuple>
    098  {
    099  /** Field UNKNOWN */
    100  public static final Fields UNKNOWN = new Fields( Kind.UNKNOWN );
    101  /** Field NONE represents a wildcard for no fields */
    102  public static final Fields NONE = new Fields( Kind.NONE );
    103  /** Field ALL represents a wildcard for all fields */
    104  public static final Fields ALL = new Fields( Kind.ALL );
    105  /** Field KEYS represents all fields used as they key for the last grouping */
    106  public static final Fields GROUP = new Fields( Kind.GROUP );
    107  /** Field VALUES represents all fields used as values for the last grouping */
    108  public static final Fields VALUES = new Fields( Kind.VALUES );
    109  /** Field ARGS represents all fields used as the arguments for the current operation */
    110  public static final Fields ARGS = new Fields( Kind.ARGS );
    111  /** Field RESULTS represents all fields returned by the current operation */
    112  public static final Fields RESULTS = new Fields( Kind.RESULTS );
    113  /** Field REPLACE represents all incoming fields, and allows their values to be replaced by the current operation results. */
    114  public static final Fields REPLACE = new Fields( Kind.REPLACE );
    115  /** Field SWAP represents all fields not used as arguments for the current operation and the operations results. */
    116  public static final Fields SWAP = new Fields( Kind.SWAP );
    117  /** Field FIRST represents the first field position, 0 */
    118  public static final Fields FIRST = new Fields( 0 );
    119  /** Field LAST represents the last field position, -1 */
    120  public static final Fields LAST = new Fields( -1 );
    121
    122  /** Field EMPTY_INT */
    123  private static final int[] EMPTY_INT = new int[ 0 ];
    124
    125  /**
    126   */
    127  enum Kind
    128    {
    129      NONE, ALL, GROUP, VALUES, ARGS, RESULTS, UNKNOWN, REPLACE, SWAP
    130    }
    131
    132  /** Field fields */
    133  Comparable[] fields = new Comparable[ 0 ];
    134  /** Field isOrdered */
    135  boolean isOrdered = true;
    136  /** Field kind */
    137  Kind kind;
    138
    139  /** Field types */
    140  Type[] types;
    141  /** Field comparators */
    142  Comparator[] comparators;
    143
    144  /** Field thisPos */
    145  transient int[] thisPos;
    146  /** Field index */
    147  transient Map<Comparable, Integer> index;
    148  /** Field posCache */
    149  transient Map<Fields, int[]> posCache;
    150  /** Field hashCode */
    151  transient int hashCode; // need to cache this
    152
    153  /**
    154   * Method fields is a convenience method to create an array of Fields instances.
    155   *
    156   * @param fields of type Fields
    157   * @return Fields[]
    158   */
    159  public static Fields[] fields( Fields... fields )
    160    {
    161    return fields;
    162    }
    163
    164  public static Comparable[] names( Comparable... names )
    165    {
    166    return names;
    167    }
    168
    169  public static Type[] types( Type... types )
    170    {
    171    return types;
    172    }
    173
    174  /**
    175   * Method size is a factory that makes new instances of Fields the given size.
    176   *
    177   * @param size of type int
    178   * @return Fields
    179   */
    180  public static Fields size( int size )
    181    {
    182    if( size == 0 )
    183      return Fields.NONE;
    184
    185    Fields fields = new Fields();
    186
    187    fields.kind = null;
    188    fields.fields = expand( size, 0 );
    189
    190    return fields;
    191    }
    192
    193  /**
    194   * Method size is a factory that makes new instances of Fields the given size with every field
    195   * of the given type.
    196   *
    197   * @param size of type int
    198   * @param type of type Type
    199   * @return Fields
    200   */
    201  public static Fields size( int size, Type type )
    202    {
    203    if( size == 0 )
    204      return Fields.NONE;
    205
    206    Fields fields = new Fields();
    207
    208    fields.kind = null;
    209    fields.fields = expand( size, 0 );
    210
    211    for( Comparable field : fields )
    212      fields = fields.applyType( field, type );
    213
    214    return fields;
    215    }
    216
    217  /**
    218   * Method join joins all given Fields instances into a new Fields instance.
    219   * <p/>
    220   * Use caution with this method, it does not assume the given Fields are either selectors or declarators. Numeric position fields are left untouched.
    221   * <p/>
    222   * If the resulting set of fields and ordinals is length zero, {@link Fields#NONE} will be returned.
    223   *
    224   * @param fields of type Fields
    225   * @return Fields
    226   */
    227  public static Fields join( Fields... fields )
    228    {
    229    return join( false, fields );
    230    }
    231
    232  public static Fields join( boolean maskDuplicateNames, Fields... fields )
    233    {
    234    int size = 0;
    235
    236    for( Fields field : fields )
    237      {
    238      if( field.isSubstitution() || field.isUnknown() )
    239        throw new TupleException( "cannot join fields if one is a substitution or is unknown" );
    240
    241      size += field.size();
    242      }
    243
    244    if( size == 0 )
    245      return Fields.NONE;
    246
    247    Comparable[] elements = join( size, fields );
    248
    249    if( maskDuplicateNames )
    250      {
    251      Set<String> names = new HashSet<String>();
    252
    253      for( int i = elements.length - 1; i >= 0; i-- )
    254        {
    255        Comparable element = elements[ i ];
    256
    257        if( names.contains( element ) )
    258          elements[ i ] = i;
    259        else if( element instanceof String )
    260          names.add( (String) element );
    261        }
    262      }
    263
    264    Type[] types = joinTypes( size, fields );
    265
    266    if( types == null )
    267      return new Fields( elements );
    268    else
    269      return new Fields( elements, types );
    270    }
    271
    272  private static Comparable[] join( int size, Fields... fields )
    273    {
    274    Comparable[] elements = expand( size, 0 );
    275
    276    int pos = 0;
    277    for( Fields field : fields )
    278      {
    279      System.arraycopy( field.fields, 0, elements, pos, field.size() );
    280      pos += field.size();
    281      }
    282
    283    return elements;
    284    }
    285
    286  private static Type[] joinTypes( int size, Fields... fields )
    287    {
    288    Type[] elements = new Type[ size ];
    289
    290    int pos = 0;
    291    for( Fields field : fields )
    292      {
    293      if( field.isNone() )
    294        continue;
    295
    296      if( field.types == null )
    297        return null;
    298
    299      System.arraycopy( field.types, 0, elements, pos, field.size() );
    300      pos += field.size();
    301      }
    302
    303    return elements;
    304    }
    305
    306  public static Fields mask( Fields fields, Fields mask )
    307    {
    308    Comparable[] elements = expand( fields.size(), 0 );
    309
    310    System.arraycopy( fields.fields, 0, elements, 0, elements.length );
    311
    312    for( int i = elements.length - 1; i >= 0; i-- )
    313      {
    314      Comparable element = elements[ i ];
    315
    316      if( element instanceof Integer )
    317        continue;
    318
    319      if( mask.getIndex().containsKey( element ) )
    320        elements[ i ] = i;
    321      }
    322
    323    return new Fields( elements );
    324    }
    325
    326  /**
    327   * Method merge merges all given Fields instances into a new Fields instance where a merge is a set union of all the
    328   * given Fields instances.
    329   * <p/>
    330   * Thus duplicate positions or field names are allowed, they are subsequently discarded in favor of the first
    331   * occurrence. That is, merging "a" and "a" would yield "a", not "a, a", yet merging "a,b" and "c" would yield "a,b,c".
    332   * <p/>
    333   * Use caution with this method, it does not assume the given Fields are either selectors or declarators. Numeric position fields are left untouched.
    334   *
    335   * @param fields of type Fields
    336   * @return Fields
    337   */
    338  public static Fields merge( Fields... fields )
    339    {
    340    List<Comparable> elements = new ArrayList<Comparable>();
    341    List<Type> elementTypes = new ArrayList<Type>();
    342
    343    for( Fields field : fields )
    344      {
    345      Type[] types = field.getTypes();
    346      int i = 0;
    347
    348      for( Comparable comparable : field )
    349        {
    350        if( !elements.contains( comparable ) )
    351          {
    352          elements.add( comparable );
    353          elementTypes.add( types == null ? null : types[ i ] ); // nulls ok
    354          }
    355
    356        i++;
    357        }
    358      }
    359
    360    Comparable[] comparables = elements.toArray( new Comparable[ elements.size() ] );
    361    Type[] types = elementTypes.toArray( new Type[ elementTypes.size() ] );
    362
    363    if( Util.containsNull( types ) )
    364      return new Fields( comparables );
    365
    366    return new Fields( comparables, types );
    367    }
    368
    369  public static Fields copyComparators( Fields toFields, Fields... fromFields )
    370    {
    371    for( Fields fromField : fromFields )
    372      {
    373      for( Comparable field : fromField )
    374        {
    375        Comparator comparator = fromField.getComparator( field );
    376
    377        if( comparator != null )
    378          toFields.setComparator( field, comparator );
    379        }
    380      }
    381
    382    return toFields;
    383    }
    384
    385  /**
    386   * Method offsetSelector is a factory that makes new instances of Fields the given size but offset by startPos.
    387   * The result Fields instance can only be used as a selector.
    388   *
    389   * @param size     of type int
    390   * @param startPos of type int
    391   * @return Fields
    392   */
    393  public static Fields offsetSelector( int size, int startPos )
    394    {
    395    Fields fields = new Fields();
    396
    397    fields.kind = null;
    398    fields.isOrdered = startPos == 0;
    399    fields.fields = expand( size, startPos );
    400
    401    return fields;
    402    }
    403
    404  private static Comparable[] expand( int size, int startPos )
    405    {
    406    if( size < 1 )
    407      throw new TupleException( "invalid size for fields: " + size );
    408
    409    if( startPos < 0 )
    410      throw new TupleException( "invalid start position for fields: " + startPos );
    411
    412    Comparable[] fields = new Comparable[ size ];
    413
    414    for( int i = 0; i < fields.length; i++ )
    415      fields[ i ] = i + startPos;
    416
    417    return fields;
    418    }
    419
    420  /**
    421   * Method resolve returns a new selector expanded on the given field declarations
    422   *
    423   * @param selector of type Fields
    424   * @param fields   of type Fields
    425   * @return Fields
    426   */
    427  public static Fields resolve( Fields selector, Fields... fields )
    428    {
    429    boolean hasUnknowns = false;
    430    int size = 0;
    431
    432    for( Fields field : fields )
    433      {
    434      if( field.isUnknown() )
    435        hasUnknowns = true;
    436
    437      if( !field.isDefined() && field.isUnOrdered() )
    438        throw new TupleException( "unable to select from field set: " + field.printVerbose() );
    439
    440      size += field.size();
    441      }
    442
    443    if( selector.isAll() )
    444      {
    445      Fields result = fields[ 0 ];
    446
    447      for( int i = 1; i < fields.length; i++ )
    448        result = result.append( fields[ i ] );
    449
    450      return result;
    451      }
    452
    453    if( selector.isReplace() )
    454      {
    455      if( fields[ 1 ].isUnknown() )
    456        throw new TupleException( "cannot replace fields with unknown field declaration" );
    457
    458      if( !fields[ 0 ].contains( fields[ 1 ] ) )
    459        throw new TupleException( "could not find all fields to be replaced, available: " + fields[ 0 ].printVerbose() + ",  declared: " + fields[ 1 ].printVerbose() );
    460
    461      Type[] types = fields[ 0 ].getTypes();
    462
    463      if( types != null )
    464        {
    465        for( int i = 1; i < fields.length; i++ )
    466          {
    467          Type[] fieldTypes = fields[ i ].getTypes();
    468
    469          if( fieldTypes == null )
    470            {
    471            fields[ 0 ] = fields[ 0 ].applyTypes( (Type[]) null );
    472            }
    473          else
    474            {
    475            for( int j = 0; j < fieldTypes.length; j++ )
    476              fields[ 0 ] = fields[ 0 ].applyType( fields[ i ].get( j ), fieldTypes[ j ] );
    477            }
    478          }
    479        }
    480
    481      return fields[ 0 ];
    482      }
    483
    484    // we can't deal with anything but ALL
    485    if( !selector.isDefined() )
    486      throw new TupleException( "unable to use given selector: " + selector );
    487
    488    Set<String> notFound = new LinkedHashSet<String>();
    489    Set<String> found = new HashSet<String>();
    490    Fields result = size( selector.size() );
    491
    492    if( hasUnknowns )
    493      size = -1;
    494
    495    Type[] types = null;
    496
    497    if( size != -1 )
    498      types = new Type[ result.size() ];
    499
    500    int offset = 0;
    501    for( Fields current : fields )
    502      {
    503      if( current.isNone() )
    504        continue;
    505
    506      resolveInto( notFound, found, selector, current, result, types, offset, size );
    507      offset += current.size();
    508      }
    509
    510    if( types != null && !Util.containsNull( types ) ) // don't apply types if any are null
    511      result = result.applyTypes( types );
    512
    513    notFound.removeAll( found );
    514
    515    if( !notFound.isEmpty() )
    516      throw new FieldsResolverException( new Fields( join( size, fields ) ), new Fields( notFound.toArray( new Comparable[ notFound.size() ] ) ) );
    517
    518    if( hasUnknowns )
    519      return selector;
    520
    521    return result;
    522    }
    523
    524  private static void resolveInto( Set<String> notFound, Set<String> found, Fields selector, Fields current, Fields result, Type[] types, int offset, int size )
    525    {
    526    for( int i = 0; i < selector.size(); i++ )
    527      {
    528      Comparable field = selector.get( i );
    529
    530      if( field instanceof String )
    531        {
    532        int index = current.indexOfSafe( field );
    533
    534        if( index == -1 )
    535          notFound.add( (String) field );
    536        else
    537          result.set( i, handleFound( found, field ) );
    538
    539        if( index != -1 && types != null && current.getType( index ) != null )
    540          types[ i ] = current.getType( index );
    541
    542        continue;
    543        }
    544
    545      int pos = current.translatePos( (Integer) field, size ) - offset;
    546
    547      if( pos >= current.size() || pos < 0 )
    548        continue;
    549
    550      Comparable thisField = current.get( pos );
    551
    552      if( types != null && current.getType( pos ) != null )
    553        types[ i ] = current.getType( pos );
    554
    555      if( thisField instanceof String )
    556        result.set( i, handleFound( found, thisField ) );
    557      else
    558        result.set( i, field );
    559      }
    560    }
    561
    562  private static Comparable handleFound( Set<String> found, Comparable field )
    563    {
    564    if( found.contains( field ) )
    565      throw new TupleException( "field name already exists: " + field );
    566
    567    found.add( (String) field );
    568
    569    return field;
    570    }
    571
    572  /**
    573   * Method asDeclaration returns a new Fields instance for use as a declarator based on the given fields value.
    574   * <p/>
    575   * Typically this is used to convert a selector to a declarator. Simply, all numeric position fields are replaced
    576   * by their absolute position.
    577   * <p/>
    578   * Comparators are preserved in the result.
    579   *
    580   * @param fields of type Fields
    581   * @return Fields
    582   */
    583  public static Fields asDeclaration( Fields fields )
    584    {
    585    if( fields == null )
    586      return null;
    587
    588    if( fields.isNone() )
    589      return fields;
    590
    591    if( !fields.isDefined() )
    592      return UNKNOWN;
    593
    594    if( fields.isOrdered() )
    595      return fields;
    596
    597    Fields result = size( fields.size() );
    598
    599    copy( null, result, fields, 0 );
    600
    601    result.types = copyTypes( fields.types, result.size() );
    602    result.comparators = fields.comparators;
    603
    604    return result;
    605    }
    606
    607  private static Fields asSelector( Fields fields )
    608    {
    609    if( !fields.isDefined() )
    610      return UNKNOWN;
    611
    612    return fields;
    613    }
    614
    615  /**
    616   * Constructor Fields creates a new Fields instance.
    617   *
    618   * @param kind of type Kind
    619   */
    620  protected Fields( Kind kind )
    621    {
    622    this.kind = kind;
    623    }
    624
    625  public Fields()
    626    {
    627    this.kind = Kind.NONE;
    628    }
    629
    630  /**
    631   * Constructor Fields creates a new Fields instance.
    632   *
    633   * @param fields of type Comparable...
    634   */
    635  @ConstructorProperties({"fields"})
    636  public Fields( Comparable... fields )
    637    {
    638    if( fields.length == 0 )
    639      this.kind = Kind.NONE;
    640    else
    641      this.fields = validate( fields );
    642    }
    643
    644  @ConstructorProperties({"field", "type"})
    645  public Fields( Comparable field, Type type )
    646    {
    647    this( names( field ), types( type ) );
    648    }
    649
    650  @ConstructorProperties({"fields", "types"})
    651  public Fields( Comparable[] fields, Type[] types )
    652    {
    653    this( fields );
    654
    655    if( isDefined() && types != null )
    656      {
    657      if( this.fields.length != types.length )
    658        throw new IllegalArgumentException( "given types array must be same length as fields" );
    659
    660      if( Util.containsNull( types ) )
    661        throw new IllegalArgumentException( "given types array contains null" );
    662
    663      this.types = copyTypes( types, this.fields.length );
    664      }
    665    }
    666
    667  @ConstructorProperties({"types"})
    668  public Fields( Type... types )
    669    {
    670    if( types.length == 0 )
    671      {
    672      this.kind = Kind.NONE;
    673      return;
    674      }
    675
    676    this.fields = expand( types.length, 0 );
    677
    678    if( this.fields.length != types.length )
    679      throw new IllegalArgumentException( "given types array must be same length as fields" );
    680
    681    if( Util.containsNull( types ) )
    682      throw new IllegalArgumentException( "given types array contains null" );
    683
    684    this.types = copyTypes( types, this.fields.length );
    685    }
    686
    687  /**
    688   * Method isUnOrdered returns true if this instance is unordered. That is, it has relative numeric field positions.
    689   * For example; [1,"a",2,-1]
    690   *
    691   * @return the unOrdered (type boolean) of this Fields object.
    692   */
    693  public boolean isUnOrdered()
    694    {
    695    return !isOrdered || kind == Kind.ALL;
    696    }
    697
    698  /**
    699   * Method isOrdered returns true if this instance is ordered. That is, all numeric field positions are absolute.
    700   * For example; [0,"a",2,3]
    701   *
    702   * @return the ordered (type boolean) of this Fields object.
    703   */
    704  public boolean isOrdered()
    705    {
    706    return isOrdered || kind == Kind.UNKNOWN;
    707    }
    708
    709  /**
    710   * Method isDefined returns true if this instance is not a field set like {@link #ALL} or {@link #UNKNOWN}.
    711   *
    712   * @return the defined (type boolean) of this Fields object.
    713   */
    714  public boolean isDefined()
    715    {
    716    return kind == null;
    717    }
    718
    719  /**
    720   * Method isOutSelector returns true if this instance is 'defined', or the field set {@link #ALL} or {@link #RESULTS}.
    721   *
    722   * @return the outSelector (type boolean) of this Fields object.
    723   */
    724  public boolean isOutSelector()
    725    {
    726    return isAll() || isResults() || isReplace() || isSwap() || isDefined();
    727    }
    728
    729  /**
    730   * Method isArgSelector returns true if this instance is 'defined' or the field set {@link #ALL}, {@link #GROUP}, or
    731   * {@link #VALUES}.
    732   *
    733   * @return the argSelector (type boolean) of this Fields object.
    734   */
    735  public boolean isArgSelector()
    736    {
    737    return isAll() || isNone() || isGroup() || isValues() || isDefined();
    738    }
    739
    740  /**
    741   * Method isDeclarator returns true if this can be used as a declarator. Specifically if it is 'defined' or
    742   * {@link #UNKNOWN}, {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}.
    743   *
    744   * @return the declarator (type boolean) of this Fields object.
    745   */
    746  public boolean isDeclarator()
    747    {
    748    return isUnknown() || isNone() || isAll() || isArguments() || isGroup() || isValues() || isDefined();
    749    }
    750
    751  /**
    752   * Method isNone returns returns true if this instance is the {@link #NONE} field set.
    753   *
    754   * @return the none (type boolean) of this Fields object.
    755   */
    756  public boolean isNone()
    757    {
    758    return kind == Kind.NONE;
    759    }
    760
    761  /**
    762   * Method isAll returns true if this instance is the {@link #ALL} field set.
    763   *
    764   * @return the all (type boolean) of this Fields object.
    765   */
    766  public boolean isAll()
    767    {
    768    return kind == Kind.ALL;
    769    }
    770
    771  /**
    772   * Method isUnknown returns true if this instance is the {@link #UNKNOWN} field set.
    773   *
    774   * @return the unknown (type boolean) of this Fields object.
    775   */
    776  public boolean isUnknown()
    777    {
    778    return kind == Kind.UNKNOWN;
    779    }
    780
    781  /**
    782   * Method isArguments returns true if this instance is the {@link #ARGS} field set.
    783   *
    784   * @return the arguments (type boolean) of this Fields object.
    785   */
    786  public boolean isArguments()
    787    {
    788    return kind == Kind.ARGS;
    789    }
    790
    791  /**
    792   * Method isValues returns true if this instance is the {@link #VALUES} field set.
    793   *
    794   * @return the values (type boolean) of this Fields object.
    795   */
    796  public boolean isValues()
    797    {
    798    return kind == Kind.VALUES;
    799    }
    800
    801  /**
    802   * Method isResults returns true if this instance is the {@link #RESULTS} field set.
    803   *
    804   * @return the results (type boolean) of this Fields object.
    805   */
    806  public boolean isResults()
    807    {
    808    return kind == Kind.RESULTS;
    809    }
    810
    811  /**
    812   * Method isReplace returns true if this instance is the {@link #REPLACE} field set.
    813   *
    814   * @return the replace (type boolean) of this Fields object.
    815   */
    816  public boolean isReplace()
    817    {
    818    return kind == Kind.REPLACE;
    819    }
    820
    821  /**
    822   * Method isSwap returns true if this instance is the {@link #SWAP} field set.
    823   *
    824   * @return the swap (type boolean) of this Fields object.
    825   */
    826  public boolean isSwap()
    827    {
    828    return kind == Kind.SWAP;
    829    }
    830
    831  /**
    832   * Method isKeys returns true if this instance is the {@link #GROUP} field set.
    833   *
    834   * @return the keys (type boolean) of this Fields object.
    835   */
    836  public boolean isGroup()
    837    {
    838    return kind == Kind.GROUP;
    839    }
    840
    841  /**
    842   * Method isSubstitution returns true if this instance is a substitution fields set. Specifically if it is the field
    843   * set {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}.
    844   *
    845   * @return the substitution (type boolean) of this Fields object.
    846   */
    847  public boolean isSubstitution()
    848    {
    849    return isAll() || isArguments() || isGroup() || isValues();
    850    }
    851
    852  private Comparable[] validate( Comparable[] fields )
    853    {
    854    isOrdered = true;
    855
    856    Set<Comparable> names = new HashSet<Comparable>();
    857
    858    for( int i = 0; i < fields.length; i++ )
    859      {
    860      Comparable field = fields[ i ];
    861
    862      if( !( field instanceof String || field instanceof Integer ) )
    863        throw new IllegalArgumentException( String.format( "invalid field type (%s); must be String or Integer: ", field ) );
    864
    865      if( names.contains( field ) )
    866        throw new IllegalArgumentException( "duplicate field name found: " + field );
    867
    868      names.add( field );
    869
    870      if( field instanceof Number && (Integer) field != i )
    871        isOrdered = false;
    872      }
    873
    874    return fields;
    875    }
    876
    877  final Comparable[] get()
    878    {
    879    return fields;
    880    }
    881
    882  /**
    883   * Method get returns the field name or position at the given index i.
    884   *
    885   * @param i is of type int
    886   * @return Comparable
    887   */
    888  public final Comparable get( int i )
    889    {
    890    return fields[ i ];
    891    }
    892
    893  final void set( int i, Comparable comparable )
    894    {
    895    fields[ i ] = comparable;
    896
    897    if( isOrdered() && comparable instanceof Integer )
    898      isOrdered = i == (Integer) comparable;
    899    }
    900
    901  /**
    902   * Method getPos returns the pos array of this Fields object.
    903   *
    904   * @return the pos (type int[]) of this Fields object.
    905   */
    906  public int[] getPos()
    907    {
    908    if( thisPos != null )
    909      return thisPos; // do not clone
    910
    911    if( isAll() || isUnknown() )
    912      thisPos = EMPTY_INT;
    913    else
    914      thisPos = makeThisPos();
    915
    916    return thisPos;
    917    }
    918
    919  /**
    920   * Method hasRelativePos returns true if any ordinal position is relative (< 0)
    921   *
    922   * @return true if any ordinal position is relative (< 0)
    923   */
    924  public boolean hasRelativePos()
    925    {
    926    for( int i : getPos() )
    927      {
    928      if( i < 0 )
    929        return true;
    930      }
    931
    932    return false;
    933    }
    934
    935  private int[] makeThisPos()
    936    {
    937    int[] pos = new int[ size() ];
    938
    939    for( int i = 0; i < size(); i++ )
    940      {
    941      Comparable field = get( i );
    942
    943      if( field instanceof Number )
    944        pos[ i ] = (Integer) field;
    945      else
    946        pos[ i ] = i;
    947      }
    948
    949    return pos;
    950    }
    951
    952  private final Map<Fields, int[]> getPosCache()
    953    {
    954    if( posCache == null )
    955      posCache = new HashMap<Fields, int[]>();
    956
    957    return posCache;
    958    }
    959
    960  private final int[] putReturn( Fields fields, int[] pos )
    961    {
    962    getPosCache().put( fields, pos );
    963
    964    return pos;
    965    }
    966
    967  public final int[] getPos( Fields fields )
    968    {
    969    return getPos( fields, -1 );
    970    }
    971
    972  final int[] getPos( Fields fields, int tupleSize )
    973    {
    974    // test for key, as we stuff a null value
    975    int[] pos = getPosCache().get( fields );
    976
    977    if( !isUnknown() && pos != null )
    978      return pos;
    979
    980    if( fields.isAll() )
    981      return putReturn( fields, null ); // return null, not getPos()
    982
    983    if( isAll() )
    984      return putReturn( fields, fields.getPos() );
    985
    986    // don't cache unknown
    987    if( size() == 0 && isUnknown() )
    988      return translatePos( fields, tupleSize );
    989
    990    pos = translatePos( fields, size() );
    991
    992    return putReturn( fields, pos );
    993    }
    994
    995  private int[] translatePos( Fields fields, int fieldSize )
    996    {
    997    int[] pos = new int[ fields.size() ];
    998
    999    for( int i = 0; i < fields.size(); i++ )
    1000      {
    1001      Comparable field = fields.get( i );
    1002
    1003      if( field instanceof Number )
    1004        pos[ i ] = translatePos( (Integer) field, fieldSize );
    1005      else
    1006        pos[ i ] = indexOf( field );
    1007      }
    1008
    1009    return pos;
    1010    }
    1011
    1012  final int translatePos( Integer integer )
    1013    {
    1014    return translatePos( integer, size() );
    1015    }
    1016
    1017  final int translatePos( Integer integer, int size )
    1018    {
    1019    if( size == -1 )
    1020      return integer;
    1021
    1022    if( integer < 0 )
    1023      integer = size + integer;
    1024
    1025    if( !isUnknown() && ( integer >= size || integer < 0 ) )
    1026      throw new TupleException( "position value is too large: " + integer + ", positions in field: " + size );
    1027
    1028    return integer;
    1029    }
    1030
    1031  /**
    1032   * Method getPos returns the index of the give field value in this Fields instance. The index corresponds to the
    1033   * Tuple value index in an associated Tuple instance.
    1034   *
    1035   * @param fieldName of type Comparable
    1036   * @return int
    1037   */
    1038  public int getPos( Comparable fieldName )
    1039    {
    1040    if( fieldName instanceof Number )
    1041      return translatePos( (Integer) fieldName );
    1042    else
    1043      return indexOf( fieldName );
    1044    }
    1045
    1046  private final Map<Comparable, Integer> getIndex()
    1047    {
    1048    if( index != null )
    1049      return index;
    1050
    1051    // make thread-safe by not having invalid intermediate state
    1052    Map<Comparable, Integer> local = new HashMap<Comparable, Integer>();
    1053
    1054    for( int i = 0; i < size(); i++ )
    1055      local.put( get( i ), i );
    1056
    1057    return index = local;
    1058    }
    1059
    1060  private int indexOf( Comparable fieldName )
    1061    {
    1062    Integer result = getIndex().get( fieldName );
    1063
    1064    if( result == null )
    1065      throw new FieldsResolverException( this, new Fields( fieldName ) );
    1066
    1067    return result;
    1068    }
    1069
    1070  int indexOfSafe( Comparable fieldName )
    1071    {
    1072    Integer result = getIndex().get( fieldName );
    1073
    1074    if( result == null )
    1075      return -1;
    1076
    1077    return result;
    1078    }
    1079
    1080  /**
    1081   * Method iterator return an unmodifiable iterator of field values. if {@link #isSubstitution()} returns true,
    1082   * this iterator will be empty.
    1083   *
    1084   * @return Iterator
    1085   */
    1086  public Iterator iterator()
    1087    {
    1088    return Collections.unmodifiableList( Arrays.asList( fields ) ).iterator();
    1089    }
    1090
    1091  /**
    1092   * Method select returns a new Fields instance with fields specified by the given selector.
    1093   *
    1094   * @param selector of type Fields
    1095   * @return Fields
    1096   */
    1097  public Fields select( Fields selector )
    1098    {
    1099    if( !isOrdered() )
    1100      throw new TupleException( "this fields instance can only be used as a selector" );
    1101
    1102    if( selector.isAll() )
    1103      return this;
    1104
    1105    // supports -1_UNKNOWN_RETURNED
    1106    // guarantees pos arguments remain selector positions, not absolute positions
    1107    if( isUnknown() )
    1108      return asSelector( selector );
    1109
    1110    if( selector.isNone() )
    1111      return NONE;
    1112
    1113    Fields result = size( selector.size() );
    1114
    1115    for( int i = 0; i < selector.size(); i++ )
    1116      {
    1117      Comparable field = selector.get( i );
    1118
    1119      if( field instanceof String )
    1120        {
    1121        result.set( i, get( indexOf( field ) ) );
    1122        continue;
    1123        }
    1124
    1125      int pos = translatePos( (Integer) field );
    1126
    1127      if( this.get( pos ) instanceof String )
    1128        result.set( i, this.get( pos ) );
    1129      else
    1130        result.set( i, pos ); // use absolute position if no field name
    1131      }
    1132
    1133    if( this.types != null )
    1134      {
    1135      result.types = new Type[ result.size() ];
    1136
    1137      for( int i = 0; i < selector.size(); i++ )
    1138        {
    1139        Comparable field = selector.get( i );
    1140
    1141        if( field instanceof String )
    1142          result.setType( i, getType( indexOf( field ) ) );
    1143        else
    1144          result.setType( i, getType( translatePos( (Integer) field ) ) );
    1145        }
    1146      }
    1147
    1148    return result;
    1149    }
    1150
    1151  /**
    1152   * Method selectPos returns a Fields instance with only positional fields, no field names.
    1153   *
    1154   * @param selector of type Fields
    1155   * @return Fields instance with only positions.
    1156   */
    1157  public Fields selectPos( Fields selector )
    1158    {
    1159    return selectPos( selector, 0 );
    1160    }
    1161
    1162  /**
    1163   * Method selectPos returns a Fields instance with only positional fields, offset by given offset value, no field names.
    1164   *
    1165   * @param selector of type Fields
    1166   * @param offset   of type int
    1167   * @return Fields instance with only positions.
    1168   */
    1169  public Fields selectPos( Fields selector, int offset )
    1170    {
    1171    int[] pos = getPos( selector );
    1172
    1173    Fields results = size( pos.length );
    1174
    1175    for( int i = 0; i < pos.length; i++ )
    1176      results.fields[ i ] = pos[ i ] + offset;
    1177
    1178    return results;
    1179    }
    1180
    1181  /**
    1182   * Method subtract returns the difference between this instance and the given fields instance.
    1183   * <p/>
    1184   * See {@link #append(Fields)} for adding field names.
    1185   *
    1186   * @param fields of type Fields
    1187   * @return Fields
    1188   */
    1189  public Fields subtract( Fields fields )
    1190    {
    1191    if( fields.isAll() )
    1192      return Fields.NONE;
    1193
    1194    if( fields.isNone() )
    1195      return this;
    1196
    1197    List<Comparable> list = new LinkedList<Comparable>();
    1198    Collections.addAll( list, this.get() );
    1199    int[] pos = getPos( fields, -1 );
    1200
    1201    for( int i : pos )
    1202      list.set( i, null );
    1203
    1204    Util.removeAllNulls( list );
    1205
    1206    Type[] newTypes = null;
    1207
    1208    if( this.types != null )
    1209      {
    1210      List<Type> types = new LinkedList<Type>();
    1211      Collections.addAll( types, this.types );
    1212
    1213      for( int i : pos )
    1214        types.set( i, null );
    1215
    1216      Util.removeAllNulls( types );
    1217
    1218      newTypes = types.toArray( new Type[ types.size() ] );
    1219      }
    1220
    1221    return new Fields( list.toArray( new Comparable[ list.size() ] ), newTypes );
    1222    }
    1223
    1224  /**
    1225   * Method is used for appending the given Fields instance to this instance, into a new Fields instance suitable
    1226   * for use as a field declaration.
    1227   * <p/>
    1228   * That is, any positional elements (including relative positions like {@code -1}, will be ignored during the
    1229   * append. For example, the second {@code 0} position is lost in the result.
    1230   * <p/>
    1231   * {@code assert new Fields( 0, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 0, "a", 2, "b" )}
    1232   * <p/>
    1233   * See {@link #subtract(Fields)} for removing field names.
    1234   *
    1235   * @param fields of type Fields
    1236   * @return Fields
    1237   */
    1238  public Fields append( Fields fields )
    1239    {
    1240    return appendInternal( fields, false );
    1241    }
    1242
    1243  /**
    1244   * Method is used for appending the given Fields instance to this instance, into a new Fields instance
    1245   * suitable for use as a field selector.
    1246   * <p/>
    1247   * That is, any positional elements will be retained during the append. For example, the {@code 5} and {@code 0}
    1248   * are retained in the result.
    1249   * <p/>
    1250   * {@code assert new Fields( 5, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 5, "a", 0, "b" )}
    1251   * <p/>
    1252   * Note any relative positional elements are retained, thus appending two Fields each declaring {@code -1}
    1253   * position will result in a TupleException noting duplicate fields.
    1254   * <p/>
    1255   * See {@link #subtract(Fields)} for removing field names.
    1256   *
    1257   * @param fields of type Fields
    1258   * @return Fields
    1259   */
    1260  public Fields appendSelector( Fields fields )
    1261    {
    1262    return appendInternal( fields, true );
    1263    }
    1264
    1265  private Fields appendInternal( Fields fields, boolean isSelect )
    1266    {
    1267    if( fields == null )
    1268      return this;
    1269
    1270    // allow unordered fields to be appended to build more complex selectors
    1271    if( this.isAll() || fields.isAll() )
    1272      throw new TupleException( "cannot append fields: " + this.print() + " + " + fields.print() );
    1273
    1274    if( ( this.isUnknown() || this.size() == 0 ) && fields.isUnknown() )
    1275      return UNKNOWN;
    1276
    1277    if( fields.isNone() )
    1278      return this;
    1279
    1280    if( this.isNone() )
    1281      return fields;
    1282
    1283    Set<Comparable> names = new HashSet<Comparable>();
    1284
    1285    // init the Field
    1286    Fields result = size( this.size() + fields.size() );
    1287
    1288    // copy over field names from this side
    1289    copyRetain( names, result, this, 0, isSelect );
    1290    // copy over field names from that side
    1291    copyRetain( names, result, fields, this.size(), isSelect );
    1292
    1293    if( this.isUnknown() || fields.isUnknown() )
    1294      result.kind = Kind.UNKNOWN;
    1295
    1296    if( ( this.isNone() || this.types != null ) && fields.types != null )
    1297      {
    1298      result.types = new Type[ this.size() + fields.size() ];
    1299
    1300      if( this.types != null ) // supports appending to NONE
    1301        System.arraycopy( this.types, 0, result.types, 0, this.size() );
    1302
    1303      System.arraycopy( fields.types, 0, result.types, this.size(), fields.size() );
    1304      }
    1305
    1306    return result;
    1307    }
    1308
    1309  /**
    1310   * Method rename will rename the from fields to the values in to to fields. Fields may contain field names, or
    1311   * positions.
    1312   * <p/>
    1313   * Using positions is useful to remove a field name put keep its place in the Tuple stream.
    1314   *
    1315   * @param from of type Fields
    1316   * @param to   of type Fields
    1317   * @return Fields
    1318   */
    1319  public Fields rename( Fields from, Fields to )
    1320    {
    1321    if( this.isSubstitution() || this.isUnknown() )
    1322      throw new TupleException( "cannot rename fields in a substitution or unknown Fields instance: " + this.print() );
    1323
    1324    if( from.size() != to.size() )
    1325      throw new TupleException( "from and to fields must be the same size" );
    1326
    1327    if( from.isSubstitution() || from.isUnknown() )
    1328      throw new TupleException( "from fields may not be a substitution or unknown" );
    1329
    1330    if( to.isSubstitution() || to.isUnknown() )
    1331      throw new TupleException( "to fields may not be a substitution or unknown" );
    1332
    1333    Comparable[] newFields = Arrays.copyOf( this.fields, this.fields.length );
    1334
    1335    int[] pos = getPos( from );
    1336
    1337    for( int i = 0; i < pos.length; i++ )
    1338      newFields[ pos[ i ] ] = to.fields[ i ];
    1339
    1340    Type[] newTypes = null;
    1341
    1342    if( this.types != null && to.types != null )
    1343      {
    1344      newTypes = copyTypes( this.types, this.size() );
    1345
    1346      for( int i = 0; i < pos.length; i++ )
    1347        newTypes[ pos[ i ] ] = to.types[ i ];
    1348      }
    1349
    1350    return new Fields( newFields, newTypes );
    1351    }
    1352
    1353  /**
    1354   * Method project will return a new Fields instance similar to the given fields instance
    1355   * except any absolute positional elements will be replaced by the current field names, if any.
    1356   *
    1357   * @param fields of type Fields
    1358   * @return Fields
    1359   */
    1360  public Fields project( Fields fields )
    1361    {
    1362    if( fields == null )
    1363      return this;
    1364
    1365    Fields results = size( fields.size() ).applyTypes( fields.getTypes() );
    1366
    1367    for( int i = 0; i < fields.fields.length; i++ )
    1368      {
    1369      if( fields.fields[ i ] instanceof String )
    1370        results.fields[ i ] = fields.fields[ i ];
    1371      else if( this.fields[ i ] instanceof String )
    1372        results.fields[ i ] = this.fields[ i ];
    1373      else
    1374        results.fields[ i ] = i;
    1375      }
    1376
    1377    return results;
    1378    }
    1379
    1380  private static void copy( Set<String> names, Fields result, Fields fields, int offset )
    1381    {
    1382    for( int i = 0; i < fields.size(); i++ )
    1383      {
    1384      Comparable field = fields.get( i );
    1385
    1386      if( !( field instanceof String ) )
    1387        continue;
    1388
    1389      if( names != null )
    1390        {
    1391        if( names.contains( field ) )
    1392          throw new TupleException( "field name already exists: " + field );
    1393
    1394        names.add( (String) field );
    1395        }
    1396
    1397      result.set( i + offset, field );
    1398      }
    1399    }
    1400
    1401  /**
    1402   * Retains any relative positional elements like -1, but checks for duplicates
    1403   *
    1404   * @param names
    1405   * @param result
    1406   * @param fields
    1407   * @param offset
    1408   * @param isSelect
    1409   */
    1410  private static void copyRetain( Set<Comparable> names, Fields result, Fields fields, int offset, boolean isSelect )
    1411    {
    1412    for( int i = 0; i < fields.size(); i++ )
    1413      {
    1414      Comparable field = fields.get( i );
    1415
    1416      if( !isSelect && field instanceof Integer )
    1417        continue;
    1418
    1419      if( names != null )
    1420        {
    1421        if( names.contains( field ) )
    1422          throw new TupleException( "field name already exists: " + field );
    1423
    1424        names.add( field );
    1425        }
    1426
    1427      result.set( i + offset, field );
    1428      }
    1429    }
    1430
    1431  /**
    1432   * Method verifyContains tests if this instance contains the field names and positions specified in the given
    1433   * fields instance. If the test fails, a {@link TupleException} is thrown.
    1434   *
    1435   * @param fields of type Fields
    1436   * @throws TupleException when one or more fields are not contained in this instance.
    1437   */
    1438  public void verifyContains( Fields fields )
    1439    {
    1440    if( isUnknown() )
    1441      return;
    1442
    1443    try
    1444      {
    1445      getPos( fields );
    1446      }
    1447    catch( TupleException exception )
    1448      {
    1449      throw new TupleException( "these fields " + print() + ", do not contain " + fields.print() );
    1450      }
    1451    }
    1452
    1453  /**
    1454   * Method contains returns true if this instance contains the field names and positions specified in the given
    1455   * fields instance.
    1456   *
    1457   * @param fields of type Fields
    1458   * @return boolean
    1459   */
    1460  public boolean contains( Fields fields )
    1461    {
    1462    try
    1463      {
    1464      getPos( fields );
    1465      return true;
    1466      }
    1467    catch( Exception exception )
    1468      {
    1469      return false;
    1470      }
    1471    }
    1472
    1473  /**
    1474   * Method compareTo compares this instance to the given Fields instance.
    1475   *
    1476   * @param other of type Fields
    1477   * @return int
    1478   */
    1479  public int compareTo( Fields other )
    1480    {
    1481    if( other.size() != size() )
    1482      return other.size() < size() ? 1 : -1;
    1483
    1484    for( int i = 0; i < size(); i++ )
    1485      {
    1486      int c = get( i ).compareTo( other.get( i ) );
    1487
    1488      if( c != 0 )
    1489        return c;
    1490      }
    1491
    1492    return 0;
    1493    }
    1494
    1495  /**
    1496   * Method compareTo implements {@link Comparable#compareTo(Object)}.
    1497   *
    1498   * @param other of type Object
    1499   * @return int
    1500   */
    1501  public int compareTo( Object other )
    1502    {
    1503    if( other instanceof Fields )
    1504      return compareTo( (Fields) other );
    1505    else
    1506      return -1;
    1507    }
    1508
    1509  /**
    1510   * Method print returns a String representation of this instance.
    1511   *
    1512   * @return String
    1513   */
    1514  public String print()
    1515    {
    1516    return "[" + toString() + "]";
    1517    }
    1518
    1519  /**
    1520   * Method printLong returns a String representation of this instance along with the size.
    1521   *
    1522   * @return String
    1523   */
    1524  public String printVerbose()
    1525    {
    1526    String fieldsString = toString();
    1527
    1528    return "[{" + ( isDefined() ? size() : "?" ) + "}:" + fieldsString + "]";
    1529    }
    1530
    1531  @Override
    1532  public String toString()
    1533    {
    1534    String string;
    1535
    1536    if( isOrdered() )
    1537      string = orderedToString();
    1538    else
    1539      string = unorderedToString();
    1540
    1541    if( types != null )
    1542      string += " | " + Util.join( Util.simpleTypeNames( types ), ", " );
    1543
    1544    return string;
    1545    }
    1546
    1547  private String orderedToString()
    1548    {
    1549    StringBuffer buffer = new StringBuffer();
    1550
    1551    if( size() != 0 )
    1552      {
    1553      int startIndex = get( 0 ) instanceof Number ? (Integer) get( 0 ) : 0;
    1554
    1555      for( int i = 0; i < size(); i++ )
    1556        {
    1557        Comparable field = get( i );
    1558
    1559        if( field instanceof Number )
    1560          {
    1561          if( i + 1 == size() || !( get( i + 1 ) instanceof Number ) )
    1562            {
    1563            if( buffer.length() != 0 )
    1564              buffer.append( ", " );
    1565
    1566            if( startIndex != i )
    1567              buffer.append( startIndex ).append( ":" ).append( field );
    1568            else
    1569              buffer.append( i );
    1570
    1571            startIndex = i;
    1572            }
    1573
    1574          continue;
    1575          }
    1576
    1577        if( i != 0 )
    1578          buffer.append( ", " );
    1579
    1580        if( field instanceof String )
    1581          buffer.append( "\'" ).append( field ).append( "\'" );
    1582        else if( field instanceof Fields )
    1583          buffer.append( ( (Fields) field ).print() );
    1584
    1585        startIndex = i + 1;
    1586        }
    1587      }
    1588
    1589    if( kind != null )
    1590      {
    1591      if( buffer.length() != 0 )
    1592        buffer.append( ", " );
    1593      buffer.append( kind );
    1594      }
    1595
    1596    return buffer.toString();
    1597    }
    1598
    1599  private String unorderedToString()
    1600    {
    1601    StringBuffer buffer = new StringBuffer();
    1602
    1603    for( Object field : get() )
    1604      {
    1605      if( buffer.length() != 0 )
    1606        buffer.append( ", " );
    1607
    1608      if( field instanceof String )
    1609        buffer.append( "\'" ).append( field ).append( "\'" );
    1610      else if( field instanceof Fields )
    1611        buffer.append( ( (Fields) field ).print() );
    1612      else
    1613        buffer.append( field );
    1614      }
    1615
    1616    if( kind != null )
    1617      {
    1618      if( buffer.length() != 0 )
    1619        buffer.append( ", " );
    1620      buffer.append( kind );
    1621      }
    1622
    1623    return buffer.toString();
    1624    }
    1625
    1626  /**
    1627   * Method size returns the number of field positions in this instance.
    1628   *
    1629   * @return int
    1630   */
    1631  public final int size()
    1632    {
    1633    return fields.length;
    1634    }
    1635
    1636  /**
    1637   * Method applyFields returns a new Fields instance with the given field names, replacing any existing type
    1638   * information within the new instance.
    1639   * <p/>
    1640   * The Comparable array must be the same length as the number for fields in this instance.
    1641   *
    1642   * @param fields the field names of this Fields object.
    1643   * @return returns a new instance of Fields with this instances types and the given field names
    1644   */
    1645  public Fields applyFields( Comparable... fields )
    1646    {
    1647    Fields result = new Fields( fields );
    1648
    1649    if( types == null )
    1650      return result;
    1651
    1652    if( types.length != result.size() )
    1653      throw new IllegalArgumentException( "given number of field names must match current fields size" );
    1654
    1655    result.types = copyTypes( types, types.length ); // make copy as Class[] could be passed in
    1656
    1657    return result;
    1658    }
    1659
    1660  /**
    1661   * Method applyType should be used to associate a {@link java.lang.reflect.Type} with a given field name or position.
    1662   * A new instance of Fields will be returned, this instance will not be modified.
    1663   * <p/>
    1664   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
    1665   * be considered.
    1666   *
    1667   * @param fieldName of type Comparable
    1668   * @param type      of type Type
    1669   */
    1670  public Fields applyType( Comparable fieldName, Type type )
    1671    {
    1672    if( type == null )
    1673      throw new IllegalArgumentException( "given type must not be null" );
    1674
    1675    int pos;
    1676
    1677    try
    1678      {
    1679      pos = getPos( asFieldName( fieldName ) );
    1680      }
    1681    catch( FieldsResolverException exception )
    1682      {
    1683      throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception );
    1684      }
    1685
    1686    Fields results = new Fields( fields );
    1687
    1688    results.types = this.types == null ? new Type[ size() ] : copyTypes( this.types, this.types.length );
    1689    results.types[ pos ] = type;
    1690
    1691    return results;
    1692    }
    1693
    1694  /**
    1695   * Method applyType should be used to associate a {@link java.lang.reflect.Type} with all positions in the current instance.
    1696   * A new instance of Fields will be returned, this instance will not be modified.
    1697   *
    1698   * @param type of type Type
    1699   */
    1700  public Fields applyTypeToAll( Type type )
    1701    {
    1702    Fields result = new Fields( fields );
    1703
    1704    if( type == null ) // allows for type erasure
    1705      return result;
    1706
    1707    Type[] copy = new Type[ result.size() ];
    1708
    1709    Arrays.fill( copy, type );
    1710
    1711    result.types = copy;
    1712
    1713    return result;
    1714    }
    1715
    1716  /**
    1717   * Method applyType should be used to associate {@link java.lang.reflect.Type} with a given field name or position
    1718   * as declared in the given Fields parameter.
    1719   * <p/>
    1720   * A new instance of Fields will be returned, this instance will not be modified.
    1721   * <p/>
    1722   *
    1723   * @param fields of type Fields
    1724   */
    1725  public Fields applyTypes( Fields fields )
    1726    {
    1727    Fields result = new Fields( this.fields, this.types );
    1728
    1729    for( Comparable field : fields )
    1730      result = result.applyType( field, fields.getType( fields.getPos( field ) ) );
    1731
    1732    return result;
    1733    }
    1734
    1735  /**
    1736   * Method applyTypes returns a new Fields instance with the given types, replacing any existing type
    1737   * information within the new instance.
    1738   * <p/>
    1739   * The Class array must be the same length as the number for fields in this instance.
    1740   * <p>
    1741   * If no values are given, the resulting Fields instance will have no type information.
    1742   *
    1743   * @param types the class types of this Fields object.
    1744   * @return returns a new instance of Fields with this instances field names and the given types, if any
    1745   */
    1746  public Fields applyTypes( Type... types )
    1747    {
    1748    Fields result = new Fields( fields );
    1749
    1750    if( types == null || types.length == 0 ) // allows for type erasure
    1751      return result;
    1752
    1753    if( types.length != size() )
    1754      throw new IllegalArgumentException( "given number of class instances must match fields size" );
    1755
    1756    for( Type type : types )
    1757      {
    1758      if( type == null )
    1759        throw new IllegalArgumentException( "type must not be null" );
    1760      }
    1761
    1762    result.types = copyTypes( types, types.length ); // make copy as Class[] could be passed in
    1763
    1764    return result;
    1765    }
    1766
    1767  /**
    1768   * Method unApplyTypes returns a new Fields instance without any type information.
    1769   *
    1770   * @return returns a new instance of Fields with this instances field names and no type information
    1771   */
    1772  public Fields unApplyTypes()
    1773    {
    1774    return applyTypes();
    1775    }
    1776
    1777  /**
    1778   * Returns the Type at the given position or having the fieldName.
    1779   *
    1780   * @param fieldName of type String or Number
    1781   * @return the Type
    1782   */
    1783  public Type getType( Comparable fieldName )
    1784    {
    1785    if( !hasTypes() )
    1786      return null;
    1787
    1788    return getType( getPos( fieldName ) );
    1789    }
    1790
    1791  public Type getType( int pos )
    1792    {
    1793    if( !hasTypes() )
    1794      return null;
    1795
    1796    return this.types[ pos ];
    1797    }
    1798
    1799  /**
    1800   * Returns the Class for the given position value.
    1801   * <p/>
    1802   * If the underlying value is of type {@link CoercibleType}, the result of
    1803   * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned.
    1804   *
    1805   * @param fieldName of type String or Number
    1806   * @return type Class
    1807   */
    1808  public Class getTypeClass( Comparable fieldName )
    1809    {
    1810    if( !hasTypes() )
    1811      return null;
    1812
    1813    return getTypeClass( getPos( fieldName ) );
    1814    }
    1815
    1816  public Class getTypeClass( int pos )
    1817    {
    1818    Type type = getType( pos );
    1819
    1820    if( type instanceof CoercibleType )
    1821      return ( (CoercibleType) type ).getCanonicalType();
    1822
    1823    return (Class) type;
    1824    }
    1825
    1826  protected void setType( int pos, Type type )
    1827    {
    1828    if( type == null )
    1829      throw new IllegalArgumentException( "type may not be null" );
    1830
    1831    this.types[ pos ] = type;
    1832    }
    1833
    1834  /**
    1835   * Returns a copy of the current types Type[] if any, else null.
    1836   *
    1837   * @return of type Type[]
    1838   */
    1839  public Type[] getTypes()
    1840    {
    1841    return copyTypes( types, size() );
    1842    }
    1843
    1844  /**
    1845   * Returns a copy of the current types Class[] if any, else null.
    1846   * <p/>
    1847   * If any underlying value is of type {@link CoercibleType}, the result of
    1848   * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned.
    1849   *
    1850   * @return of type Class
    1851   */
    1852  public Class[] getTypesClasses()
    1853    {
    1854    if( types == null )
    1855      return null;
    1856
    1857    Class[] classes = new Class[ types.length ];
    1858
    1859    for( int i = 0; i < types.length; i++ )
    1860      {
    1861      if( types[ i ] instanceof CoercibleType )
    1862        classes[ i ] = ( (CoercibleType) types[ i ] ).getCanonicalType();
    1863      else
    1864        classes[ i ] = (Class) types[ i ]; // this throws a more helpful exception vs arraycopy
    1865      }
    1866
    1867    return classes;
    1868    }
    1869
    1870  private static Type[] copyTypes( Type[] types, int size )
    1871    {
    1872    if( types == null )
    1873      return null;
    1874
    1875    Type[] copy = new Type[ size ];
    1876
    1877    if( types.length != size )
    1878      throw new IllegalArgumentException( "types array must be same size as fields array" );
    1879
    1880    System.arraycopy( types, 0, copy, 0, size );
    1881
    1882    return copy;
    1883    }
    1884
    1885  /**
    1886   * Returns true if there are types associated with this instance.
    1887   *
    1888   * @return boolean
    1889   */
    1890  public final boolean hasTypes()
    1891    {
    1892    return types != null;
    1893    }
    1894
    1895  /**
    1896   * Method setComparator should be used to associate a {@link java.util.Comparator} with a given field name or position.
    1897   * <p/>
    1898   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
    1899   * be considered.
    1900   *
    1901   * @param fieldName  of type Comparable
    1902   * @param comparator of type Comparator
    1903   */
    1904  public void setComparator( Comparable fieldName, Comparator comparator )
    1905    {
    1906    if( !( comparator instanceof Serializable ) )
    1907      throw new IllegalArgumentException( "given comparator must be serializable" );
    1908
    1909    if( comparators == null )
    1910      comparators = new Comparator[ size() ];
    1911
    1912    try
    1913      {
    1914      comparators[ getPos( asFieldName( fieldName ) ) ] = comparator;
    1915      }
    1916    catch( FieldsResolverException exception )
    1917      {
    1918      throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception );
    1919      }
    1920    }
    1921
    1922  /**
    1923   * Method setComparators sets all the comparators of this Fields object. The Comparator array
    1924   * must be the same length as the number for fields in this instance.
    1925   *
    1926   * @param comparators the comparators of this Fields object.
    1927   */
    1928  public void setComparators( Comparator... comparators )
    1929    {
    1930    if( comparators.length != size() )
    1931      throw new IllegalArgumentException( "given number of comparator instances must match fields size" );
    1932
    1933    for( Comparator comparator : comparators )
    1934      {
    1935      if( !( comparator instanceof Serializable ) )
    1936        throw new IllegalArgumentException( "comparators must be serializable" );
    1937      }
    1938
    1939    this.comparators = comparators;
    1940    }
    1941
    1942  protected static Comparable asFieldName( Comparable fieldName )
    1943    {
    1944    if( fieldName instanceof Fields )
    1945      {
    1946      Fields fields = (Fields) fieldName;
    1947
    1948      if( !fields.isDefined() )
    1949        throw new TupleException( "given Fields instance must explicitly declare one field name or position: " + fields.printVerbose() );
    1950
    1951      fieldName = fields.get( 0 );
    1952      }
    1953
    1954    return fieldName;
    1955    }
    1956
    1957  protected Comparator getComparator( Comparable fieldName )
    1958    {
    1959    if( comparators == null )
    1960      return null;
    1961
    1962    try
    1963      {
    1964      return comparators[ getPos( asFieldName( fieldName ) ) ];
    1965      }
    1966    catch( FieldsResolverException exception )
    1967      {
    1968      return null;
    1969      }
    1970    }
    1971
    1972  /**
    1973   * Method getComparators returns the comparators of this Fields object.
    1974   *
    1975   * @return the comparators (type Comparator[]) of this Fields object.
    1976   */
    1977  public Comparator[] getComparators()
    1978    {
    1979    Comparator[] copy = new Comparator[ size() ];
    1980
    1981    if( comparators != null )
    1982      System.arraycopy( comparators, 0, copy, 0, size() );
    1983
    1984    return copy;
    1985    }
    1986
    1987  /**
    1988   * Method hasComparators test if this Fields instance has Comparators.
    1989   *
    1990   * @return boolean
    1991   */
    1992  public boolean hasComparators()
    1993    {
    1994    return comparators != null;
    1995    }
    1996
    1997  @Override
    1998  public int compare( Tuple lhs, Tuple rhs )
    1999    {
    2000    return lhs.compareTo( comparators, rhs );
    2001    }
    2002
    2003  @Override
    2004  public boolean equals( Object object )
    2005    {
    2006    if( this == object )
    2007      return true;
    2008    if( object == null || getClass() != object.getClass() )
    2009      return false;
    2010
    2011    Fields fields = (Fields) object;
    2012
    2013    return equalsFields( fields ) && Arrays.equals( types, fields.types );
    2014    }
    2015
    2016  /**
    2017   * Method equalsFields compares only the internal field names and postions only between this and the given Fields
    2018   * instance. Type information is ignored.
    2019   *
    2020   * @param fields of type int
    2021   * @return true if this and the given instance have the same positions and/or field names.
    2022   */
    2023  public boolean equalsFields( Fields fields )
    2024    {
    2025    return fields != null && this.kind == fields.kind && Arrays.equals( get(), fields.get() );
    2026    }
    2027
    2028  @Override
    2029  public int hashCode()
    2030    {
    2031    if( hashCode == 0 )
    2032      hashCode = get() != null ? Arrays.hashCode( get() ) : 0;
    2033
    2034    return hashCode;
    2035    }
    2036  }