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