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