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  }