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