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