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