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[ result.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          if( Util.containsNull( types ) )
650            throw new IllegalArgumentException( "given types array contains null" );
651    
652          this.types = copyTypes( types, this.fields.length );
653          }
654        }
655    
656      /**
657       * Method isUnOrdered returns true if this instance is unordered. That is, it has relative numeric field positions.
658       * For example; [1,"a",2,-1]
659       *
660       * @return the unOrdered (type boolean) of this Fields object.
661       */
662      public boolean isUnOrdered()
663        {
664        return !isOrdered || kind == Kind.ALL;
665        }
666    
667      /**
668       * Method isOrdered returns true if this instance is ordered. That is, all numeric field positions are absolute.
669       * For example; [0,"a",2,3]
670       *
671       * @return the ordered (type boolean) of this Fields object.
672       */
673      public boolean isOrdered()
674        {
675        return isOrdered || kind == Kind.UNKNOWN;
676        }
677    
678      /**
679       * Method isDefined returns true if this instance is not a field set like {@link #ALL} or {@link #UNKNOWN}.
680       *
681       * @return the defined (type boolean) of this Fields object.
682       */
683      public boolean isDefined()
684        {
685        return kind == null;
686        }
687    
688      /**
689       * Method isOutSelector returns true if this instance is 'defined', or the field set {@link #ALL} or {@link #RESULTS}.
690       *
691       * @return the outSelector (type boolean) of this Fields object.
692       */
693      public boolean isOutSelector()
694        {
695        return isAll() || isResults() || isReplace() || isSwap() || isDefined();
696        }
697    
698      /**
699       * Method isArgSelector returns true if this instance is 'defined' or the field set {@link #ALL}, {@link #GROUP}, or
700       * {@link #VALUES}.
701       *
702       * @return the argSelector (type boolean) of this Fields object.
703       */
704      public boolean isArgSelector()
705        {
706        return isAll() || isNone() || isGroup() || isValues() || isDefined();
707        }
708    
709      /**
710       * Method isDeclarator returns true if this can be used as a declarator. Specifically if it is 'defined' or
711       * {@link #UNKNOWN}, {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}.
712       *
713       * @return the declarator (type boolean) of this Fields object.
714       */
715      public boolean isDeclarator()
716        {
717        return isUnknown() || isNone() || isAll() || isArguments() || isGroup() || isValues() || isDefined();
718        }
719    
720      /**
721       * Method isNone returns returns true if this instance is the {@link #NONE} field set.
722       *
723       * @return the none (type boolean) of this Fields object.
724       */
725      public boolean isNone()
726        {
727        return kind == Kind.NONE;
728        }
729    
730      /**
731       * Method isAll returns true if this instance is the {@link #ALL} field set.
732       *
733       * @return the all (type boolean) of this Fields object.
734       */
735      public boolean isAll()
736        {
737        return kind == Kind.ALL;
738        }
739    
740      /**
741       * Method isUnknown returns true if this instance is the {@link #UNKNOWN} field set.
742       *
743       * @return the unknown (type boolean) of this Fields object.
744       */
745      public boolean isUnknown()
746        {
747        return kind == Kind.UNKNOWN;
748        }
749    
750      /**
751       * Method isArguments returns true if this instance is the {@link #ARGS} field set.
752       *
753       * @return the arguments (type boolean) of this Fields object.
754       */
755      public boolean isArguments()
756        {
757        return kind == Kind.ARGS;
758        }
759    
760      /**
761       * Method isValues returns true if this instance is the {@link #VALUES} field set.
762       *
763       * @return the values (type boolean) of this Fields object.
764       */
765      public boolean isValues()
766        {
767        return kind == Kind.VALUES;
768        }
769    
770      /**
771       * Method isResults returns true if this instance is the {@link #RESULTS} field set.
772       *
773       * @return the results (type boolean) of this Fields object.
774       */
775      public boolean isResults()
776        {
777        return kind == Kind.RESULTS;
778        }
779    
780      /**
781       * Method isReplace returns true if this instance is the {@link #REPLACE} field set.
782       *
783       * @return the replace (type boolean) of this Fields object.
784       */
785      public boolean isReplace()
786        {
787        return kind == Kind.REPLACE;
788        }
789    
790      /**
791       * Method isSwap returns true if this instance is the {@link #SWAP} field set.
792       *
793       * @return the swap (type boolean) of this Fields object.
794       */
795      public boolean isSwap()
796        {
797        return kind == Kind.SWAP;
798        }
799    
800      /**
801       * Method isKeys returns true if this instance is the {@link #GROUP} field set.
802       *
803       * @return the keys (type boolean) of this Fields object.
804       */
805      public boolean isGroup()
806        {
807        return kind == Kind.GROUP;
808        }
809    
810      /**
811       * Method isSubstitution returns true if this instance is a substitution fields set. Specifically if it is the field
812       * set {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}.
813       *
814       * @return the substitution (type boolean) of this Fields object.
815       */
816      public boolean isSubstitution()
817        {
818        return isAll() || isArguments() || isGroup() || isValues();
819        }
820    
821      private Comparable[] validate( Comparable[] fields )
822        {
823        isOrdered = true;
824    
825        Set<Comparable> names = new HashSet<Comparable>();
826    
827        for( int i = 0; i < fields.length; i++ )
828          {
829          Comparable field = fields[ i ];
830    
831          if( !( field instanceof String || field instanceof Integer ) )
832            throw new IllegalArgumentException( String.format( "invalid field type (%s); must be String or Integer: ", field ) );
833    
834          if( names.contains( field ) )
835            throw new IllegalArgumentException( "duplicate field name found: " + field );
836    
837          names.add( field );
838    
839          if( field instanceof Number && (Integer) field != i )
840            isOrdered = false;
841          }
842    
843        return fields;
844        }
845    
846      final Comparable[] get()
847        {
848        return fields;
849        }
850    
851      /**
852       * Method get returns the field name or position at the given index i.
853       *
854       * @param i is of type int
855       * @return Comparable
856       */
857      public final Comparable get( int i )
858        {
859        return fields[ i ];
860        }
861    
862      final void set( int i, Comparable comparable )
863        {
864        fields[ i ] = comparable;
865    
866        if( isOrdered() && comparable instanceof Integer )
867          isOrdered = i == (Integer) comparable;
868        }
869    
870      /**
871       * Method getPos returns the pos array of this Fields object.
872       *
873       * @return the pos (type int[]) of this Fields object.
874       */
875      public int[] getPos()
876        {
877        if( thisPos != null )
878          return thisPos; // do not clone
879    
880        if( isAll() || isUnknown() )
881          thisPos = EMPTY_INT;
882        else
883          thisPos = makeThisPos();
884    
885        return thisPos;
886        }
887    
888      private int[] makeThisPos()
889        {
890        int[] pos = new int[ size() ];
891    
892        for( int i = 0; i < size(); i++ )
893          {
894          Comparable field = get( i );
895    
896          if( field instanceof Number )
897            pos[ i ] = (Integer) field;
898          else
899            pos[ i ] = i;
900          }
901    
902        return pos;
903        }
904    
905      private final Map<Fields, int[]> getPosCache()
906        {
907        if( posCache == null )
908          posCache = new HashMap<Fields, int[]>();
909    
910        return posCache;
911        }
912    
913      private final int[] putReturn( Fields fields, int[] pos )
914        {
915        getPosCache().put( fields, pos );
916    
917        return pos;
918        }
919    
920      public final int[] getPos( Fields fields )
921        {
922        return getPos( fields, -1 );
923        }
924    
925      final int[] getPos( Fields fields, int tupleSize )
926        {
927        // test for key, as we stuff a null value
928        if( !isUnknown() && getPosCache().containsKey( fields ) )
929          return getPosCache().get( fields );
930    
931        if( fields.isAll() )
932          return putReturn( fields, null ); // return null, not getPos()
933    
934        if( isAll() )
935          return putReturn( fields, fields.getPos() );
936    
937        // don't cache unknown
938        if( size() == 0 && isUnknown() )
939          return translatePos( fields, tupleSize );
940    
941        int[] pos = translatePos( fields, size() );
942    
943        return putReturn( fields, pos );
944        }
945    
946      private int[] translatePos( Fields fields, int fieldSize )
947        {
948        int[] pos = new int[ fields.size() ];
949    
950        for( int i = 0; i < fields.size(); i++ )
951          {
952          Comparable field = fields.get( i );
953    
954          if( field instanceof Number )
955            pos[ i ] = translatePos( (Integer) field, fieldSize );
956          else
957            pos[ i ] = indexOf( field );
958          }
959    
960        return pos;
961        }
962    
963      final int translatePos( Integer integer )
964        {
965        return translatePos( integer, size() );
966        }
967    
968      final int translatePos( Integer integer, int size )
969        {
970        if( size == -1 )
971          return integer;
972    
973        if( integer < 0 )
974          integer = size + integer;
975    
976        if( !isUnknown() && ( integer >= size || integer < 0 ) )
977          throw new TupleException( "position value is too large: " + integer + ", positions in field: " + size );
978    
979        return integer;
980        }
981    
982      /**
983       * Method getPos returns the index of the give field value in this Fields instance. The index corresponds to the
984       * Tuple value index in an associated Tuple instance.
985       *
986       * @param fieldName of type Comparable
987       * @return int
988       */
989      public int getPos( Comparable fieldName )
990        {
991        if( fieldName instanceof Number )
992          return translatePos( (Integer) fieldName );
993        else
994          return indexOf( fieldName );
995        }
996    
997      private final Map<Comparable, Integer> getIndex()
998        {
999        if( index != null )
1000          return index;
1001    
1002        // make thread-safe by not having invalid intermediate state
1003        Map<Comparable, Integer> local = new HashMap<Comparable, Integer>();
1004    
1005        for( int i = 0; i < size(); i++ )
1006          local.put( get( i ), i );
1007    
1008        return index = local;
1009        }
1010    
1011      private int indexOf( Comparable fieldName )
1012        {
1013        Integer result = getIndex().get( fieldName );
1014    
1015        if( result == null )
1016          throw new FieldsResolverException( this, new Fields( fieldName ) );
1017    
1018        return result;
1019        }
1020    
1021      int indexOfSafe( Comparable fieldName )
1022        {
1023        Integer result = getIndex().get( fieldName );
1024    
1025        if( result == null )
1026          return -1;
1027    
1028        return result;
1029        }
1030    
1031      /**
1032       * Method iterator return an unmodifiable iterator of field values. if {@link #isSubstitution()} returns true,
1033       * this iterator will be empty.
1034       *
1035       * @return Iterator
1036       */
1037      public Iterator iterator()
1038        {
1039        return Collections.unmodifiableList( Arrays.asList( fields ) ).iterator();
1040        }
1041    
1042      /**
1043       * Method select returns a new Fields instance with fields specified by the given selector.
1044       *
1045       * @param selector of type Fields
1046       * @return Fields
1047       */
1048      public Fields select( Fields selector )
1049        {
1050        if( !isOrdered() )
1051          throw new TupleException( "this fields instance can only be used as a selector" );
1052    
1053        if( selector.isAll() )
1054          return this;
1055    
1056        // supports -1_UNKNOWN_RETURNED
1057        // guarantees pos arguments remain selector positions, not absolute positions
1058        if( isUnknown() )
1059          return asSelector( selector );
1060    
1061        if( selector.isNone() )
1062          return NONE;
1063    
1064        Fields result = size( selector.size() );
1065    
1066        for( int i = 0; i < selector.size(); i++ )
1067          {
1068          Comparable field = selector.get( i );
1069    
1070          if( field instanceof String )
1071            {
1072            result.set( i, get( indexOf( field ) ) );
1073            continue;
1074            }
1075    
1076          int pos = translatePos( (Integer) field );
1077    
1078          if( this.get( pos ) instanceof String )
1079            result.set( i, this.get( pos ) );
1080          else
1081            result.set( i, pos ); // use absolute position if no field name
1082          }
1083    
1084        if( this.types != null )
1085          {
1086          result.types = new Type[ result.size() ];
1087    
1088          for( int i = 0; i < selector.size(); i++ )
1089            {
1090            Comparable field = selector.get( i );
1091    
1092            if( field instanceof String )
1093              result.setType( i, getType( indexOf( field ) ) );
1094            else
1095              result.setType( i, getType( translatePos( (Integer) field ) ) );
1096            }
1097          }
1098    
1099        return result;
1100        }
1101    
1102      /**
1103       * Method selectPos returns a Fields instance with only positional fields, no field names.
1104       *
1105       * @param selector of type Fields
1106       * @return Fields instance with only positions.
1107       */
1108      public Fields selectPos( Fields selector )
1109        {
1110        return selectPos( selector, 0 );
1111        }
1112    
1113      /**
1114       * Method selectPos returns a Fields instance with only positional fields, offset by given offset value, no field names.
1115       *
1116       * @param selector of type Fields
1117       * @param offset   of type int
1118       * @return Fields instance with only positions.
1119       */
1120      public Fields selectPos( Fields selector, int offset )
1121        {
1122        int[] pos = getPos( selector );
1123    
1124        Fields results = size( pos.length );
1125    
1126        for( int i = 0; i < pos.length; i++ )
1127          results.fields[ i ] = pos[ i ] + offset;
1128    
1129        return results;
1130        }
1131    
1132      /**
1133       * Method subtract returns the difference between this instance and the given fields instance.
1134       * <p/>
1135       * See {@link #append(Fields)} for adding field names.
1136       *
1137       * @param fields of type Fields
1138       * @return Fields
1139       */
1140      public Fields subtract( Fields fields )
1141        {
1142        if( fields.isAll() )
1143          return Fields.NONE;
1144    
1145        if( fields.isNone() )
1146          return this;
1147    
1148        List<Comparable> list = new LinkedList<Comparable>();
1149        Collections.addAll( list, this.get() );
1150        int[] pos = getPos( fields, -1 );
1151    
1152        for( int i : pos )
1153          list.set( i, null );
1154    
1155        Util.removeAllNulls( list );
1156    
1157        Type[] newTypes = null;
1158    
1159        if( this.types != null )
1160          {
1161          List<Type> types = new LinkedList<Type>();
1162          Collections.addAll( types, this.types );
1163    
1164          for( int i : pos )
1165            types.set( i, null );
1166    
1167          Util.removeAllNulls( types );
1168    
1169          newTypes = types.toArray( new Type[ types.size() ] );
1170          }
1171    
1172        return new Fields( list.toArray( new Comparable[ list.size() ] ), newTypes );
1173        }
1174    
1175      /**
1176       * Method is used for appending the given Fields instance to this instance, into a new Fields instance.
1177       * <p/>
1178       * See {@link #subtract(Fields)} for removing field names.
1179       * <p/>
1180       * This method has been deprecated, see {@link #join(Fields...)}
1181       *
1182       * @param fields of type Fields[]
1183       * @return Fields
1184       */
1185      @Deprecated
1186      public Fields append( Fields[] fields )
1187        {
1188        if( fields.length == 0 )
1189          return null;
1190    
1191        Fields field = this;
1192    
1193        for( Fields current : fields )
1194          field = field.append( current );
1195    
1196        return field;
1197        }
1198    
1199      /**
1200       * Method is used for appending the given Fields instance to this instance, into a new Fields instance suitable
1201       * for use as a field declaration.
1202       * <p/>
1203       * That is, any positional elements (including relative positions like {@code -1}, will be ignored during the
1204       * append. For example, the second {@code 0} position is lost in the result.
1205       * <p/>
1206       * {@code assert new Fields( 0, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 0, "a", 2, "b" )}
1207       * <p/>
1208       * See {@link #subtract(Fields)} for removing field names.
1209       *
1210       * @param fields of type Fields
1211       * @return Fields
1212       */
1213      public Fields append( Fields fields )
1214        {
1215        return appendInternal( fields, false );
1216        }
1217    
1218      /**
1219       * Method is used for appending the given Fields instance to this instance, into a new Fields instance
1220       * suitable for use as a field selector.
1221       * <p/>
1222       * That is, any positional elements will be retained during the append. For example, the {@code 5} and {@code 0}
1223       * are retained in the result.
1224       * <p/>
1225       * {@code assert new Fields( 5, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 5, "a", 0, "b" )}
1226       * <p/>
1227       * Note any relative positional elements are retained, thus appending two Fields each declaring {@code -1}
1228       * position will result in a TupleException noting duplicate fields.
1229       * <p/>
1230       * See {@link #subtract(Fields)} for removing field names.
1231       *
1232       * @param fields of type Fields
1233       * @return Fields
1234       */
1235      public Fields appendSelector( Fields fields )
1236        {
1237        return appendInternal( fields, true );
1238        }
1239    
1240      private Fields appendInternal( Fields fields, boolean isSelect )
1241        {
1242        if( fields == null )
1243          return this;
1244    
1245        // allow unordered fields to be appended to build more complex selectors
1246        if( this.isAll() || fields.isAll() )
1247          throw new TupleException( "cannot append fields: " + this.print() + " + " + fields.print() );
1248    
1249        if( ( this.isUnknown() || this.size() == 0 ) && fields.isUnknown() )
1250          return UNKNOWN;
1251    
1252        if( fields.isNone() )
1253          return this;
1254    
1255        if( this.isNone() )
1256          return fields;
1257    
1258        Set<Comparable> names = new HashSet<Comparable>();
1259    
1260        // init the Field
1261        Fields result = size( this.size() + fields.size() );
1262    
1263        // copy over field names from this side
1264        copyRetain( names, result, this, 0, isSelect );
1265        // copy over field names from that side
1266        copyRetain( names, result, fields, this.size(), isSelect );
1267    
1268        if( this.isUnknown() || fields.isUnknown() )
1269          result.kind = Kind.UNKNOWN;
1270    
1271        if( ( this.isNone() || this.types != null ) && fields.types != null )
1272          {
1273          result.types = new Type[ this.size() + fields.size() ];
1274    
1275          if( this.types != null ) // supports appending to NONE
1276            System.arraycopy( this.types, 0, result.types, 0, this.size() );
1277    
1278          System.arraycopy( fields.types, 0, result.types, this.size(), fields.size() );
1279          }
1280    
1281        return result;
1282        }
1283    
1284      /**
1285       * Method rename will rename the from fields to the values in to to fields. Fields may contain field names, or
1286       * positions.
1287       * <p/>
1288       * Using positions is useful to remove a field name put keep its place in the Tuple stream.
1289       *
1290       * @param from of type Fields
1291       * @param to   of type Fields
1292       * @return Fields
1293       */
1294      public Fields rename( Fields from, Fields to )
1295        {
1296        if( this.isSubstitution() || this.isUnknown() )
1297          throw new TupleException( "cannot rename fields in a substitution or unknown Fields instance: " + this.print() );
1298    
1299        if( from.size() != to.size() )
1300          throw new TupleException( "from and to fields must be the same size" );
1301    
1302        if( from.isSubstitution() || from.isUnknown() )
1303          throw new TupleException( "from fields may not be a substitution or unknown" );
1304    
1305        if( to.isSubstitution() || to.isUnknown() )
1306          throw new TupleException( "to fields may not be a substitution or unknown" );
1307    
1308        Comparable[] newFields = Arrays.copyOf( this.fields, this.fields.length );
1309    
1310        int[] pos = getPos( from );
1311    
1312        for( int i = 0; i < pos.length; i++ )
1313          newFields[ pos[ i ] ] = to.fields[ i ];
1314    
1315        Type[] newTypes = null;
1316    
1317        if( this.types != null && to.types != null )
1318          {
1319          newTypes = copyTypes( this.types, this.size() );
1320    
1321          for( int i = 0; i < pos.length; i++ )
1322            newTypes[ pos[ i ] ] = to.types[ i ];
1323          }
1324    
1325        return new Fields( newFields, newTypes );
1326        }
1327    
1328      /**
1329       * Method project will return a new Fields instance similar to the given fields instance
1330       * except any absolute positional elements will be replaced by the current field names, if any.
1331       *
1332       * @param fields of type Fields
1333       * @return Fields
1334       */
1335      public Fields project( Fields fields )
1336        {
1337        if( fields == null )
1338          return this;
1339    
1340        Fields results = size( fields.size() ).applyTypes( fields.getTypes() );
1341    
1342        for( int i = 0; i < fields.fields.length; i++ )
1343          {
1344          if( fields.fields[ i ] instanceof String )
1345            results.fields[ i ] = fields.fields[ i ];
1346          else if( this.fields[ i ] instanceof String )
1347            results.fields[ i ] = this.fields[ i ];
1348          else
1349            results.fields[ i ] = i;
1350          }
1351    
1352        return results;
1353        }
1354    
1355      private static void copy( Set<String> names, Fields result, Fields fields, int offset )
1356        {
1357        for( int i = 0; i < fields.size(); i++ )
1358          {
1359          Comparable field = fields.get( i );
1360    
1361          if( !( field instanceof String ) )
1362            continue;
1363    
1364          if( names != null )
1365            {
1366            if( names.contains( field ) )
1367              throw new TupleException( "field name already exists: " + field );
1368    
1369            names.add( (String) field );
1370            }
1371    
1372          result.set( i + offset, field );
1373          }
1374        }
1375    
1376      /**
1377       * Retains any relative positional elements like -1, but checks for duplicates
1378       *
1379       * @param names
1380       * @param result
1381       * @param fields
1382       * @param offset
1383       * @param isSelect
1384       */
1385      private static void copyRetain( Set<Comparable> names, Fields result, Fields fields, int offset, boolean isSelect )
1386        {
1387        for( int i = 0; i < fields.size(); i++ )
1388          {
1389          Comparable field = fields.get( i );
1390    
1391          if( !isSelect && field instanceof Integer )
1392            continue;
1393    
1394          if( names != null )
1395            {
1396            if( names.contains( field ) )
1397              throw new TupleException( "field name already exists: " + field );
1398    
1399            names.add( field );
1400            }
1401    
1402          result.set( i + offset, field );
1403          }
1404        }
1405    
1406      /**
1407       * Method verifyContains tests if this instance contains the field names and positions specified in the given
1408       * fields instance. If the test fails, a {@link TupleException} is thrown.
1409       *
1410       * @param fields of type Fields
1411       * @throws TupleException when one or more fields are not contained in this instance.
1412       */
1413      public void verifyContains( Fields fields )
1414        {
1415        if( isUnknown() )
1416          return;
1417    
1418        try
1419          {
1420          getPos( fields );
1421          }
1422        catch( TupleException exception )
1423          {
1424          throw new TupleException( "these fields " + print() + ", do not contain " + fields.print() );
1425          }
1426        }
1427    
1428      /**
1429       * Method contains returns true if this instance contains the field names and positions specified in the given
1430       * fields instance.
1431       *
1432       * @param fields of type Fields
1433       * @return boolean
1434       */
1435      public boolean contains( Fields fields )
1436        {
1437        try
1438          {
1439          getPos( fields );
1440          return true;
1441          }
1442        catch( Exception exception )
1443          {
1444          return false;
1445          }
1446        }
1447    
1448      /**
1449       * Method compareTo compares this instance to the given Fields instance.
1450       *
1451       * @param other of type Fields
1452       * @return int
1453       */
1454      public int compareTo( Fields other )
1455        {
1456        if( other.size() != size() )
1457          return other.size() < size() ? 1 : -1;
1458    
1459        for( int i = 0; i < size(); i++ )
1460          {
1461          int c = get( i ).compareTo( other.get( i ) );
1462    
1463          if( c != 0 )
1464            return c;
1465          }
1466    
1467        return 0;
1468        }
1469    
1470      /**
1471       * Method compareTo implements {@link Comparable#compareTo(Object)}.
1472       *
1473       * @param other of type Object
1474       * @return int
1475       */
1476      public int compareTo( Object other )
1477        {
1478        if( other instanceof Fields )
1479          return compareTo( (Fields) other );
1480        else
1481          return -1;
1482        }
1483    
1484      /**
1485       * Method print returns a String representation of this instance.
1486       *
1487       * @return String
1488       */
1489      public String print()
1490        {
1491        return "[" + toString() + "]";
1492        }
1493    
1494      /**
1495       * Method printLong returns a String representation of this instance along with the size.
1496       *
1497       * @return String
1498       */
1499      public String printVerbose()
1500        {
1501        String fieldsString = toString();
1502    
1503        return "[{" + ( isDefined() ? size() : "?" ) + "}:" + fieldsString + "]";
1504        }
1505    
1506    
1507      @Override
1508      public String toString()
1509        {
1510        String string;
1511    
1512        if( isOrdered() )
1513          string = orderedToString();
1514        else
1515          string = unorderedToString();
1516    
1517        if( types != null )
1518          string += " | " + Util.join( Util.simpleTypeNames( types ), ", " );
1519    
1520        return string;
1521        }
1522    
1523      private String orderedToString()
1524        {
1525        StringBuffer buffer = new StringBuffer();
1526    
1527        if( size() != 0 )
1528          {
1529          int startIndex = get( 0 ) instanceof Number ? (Integer) get( 0 ) : 0;
1530    
1531          for( int i = 0; i < size(); i++ )
1532            {
1533            Comparable field = get( i );
1534    
1535            if( field instanceof Number )
1536              {
1537              if( i + 1 == size() || !( get( i + 1 ) instanceof Number ) )
1538                {
1539                if( buffer.length() != 0 )
1540                  buffer.append( ", " );
1541    
1542                if( startIndex != i )
1543                  buffer.append( startIndex ).append( ":" ).append( field );
1544                else
1545                  buffer.append( i );
1546    
1547                startIndex = i;
1548                }
1549    
1550              continue;
1551              }
1552    
1553            if( i != 0 )
1554              buffer.append( ", " );
1555    
1556            if( field instanceof String )
1557              buffer.append( "\'" ).append( field ).append( "\'" );
1558            else if( field instanceof Fields )
1559              buffer.append( ( (Fields) field ).print() );
1560    
1561            startIndex = i + 1;
1562            }
1563          }
1564    
1565        if( kind != null )
1566          {
1567          if( buffer.length() != 0 )
1568            buffer.append( ", " );
1569          buffer.append( kind );
1570          }
1571    
1572        return buffer.toString();
1573        }
1574    
1575      private String unorderedToString()
1576        {
1577        StringBuffer buffer = new StringBuffer();
1578    
1579        for( Object field : get() )
1580          {
1581          if( buffer.length() != 0 )
1582            buffer.append( ", " );
1583    
1584          if( field instanceof String )
1585            buffer.append( "\'" ).append( field ).append( "\'" );
1586          else if( field instanceof Fields )
1587            buffer.append( ( (Fields) field ).print() );
1588          else
1589            buffer.append( field );
1590          }
1591    
1592        if( kind != null )
1593          {
1594          if( buffer.length() != 0 )
1595            buffer.append( ", " );
1596          buffer.append( kind );
1597          }
1598    
1599        return buffer.toString();
1600        }
1601    
1602      /**
1603       * Method size returns the number of field positions in this instance.
1604       *
1605       * @return int
1606       */
1607      public final int size()
1608        {
1609        return fields.length;
1610        }
1611    
1612      /**
1613       * Method applyType should be used to associate a {@link java.lang.reflect.Type} with a given field name or position.
1614       * A new instance of Fields will be returned, this instance will not be modified.
1615       * <p/>
1616       * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
1617       * be considered.
1618       *
1619       * @param fieldName of type Comparable
1620       * @param type      of type Type
1621       */
1622      public Fields applyType( Comparable fieldName, Type type )
1623        {
1624        if( type == null )
1625          throw new IllegalArgumentException( "given type must not be null" );
1626    
1627        int pos;
1628    
1629        try
1630          {
1631          pos = getPos( asFieldName( fieldName ) );
1632          }
1633        catch( FieldsResolverException exception )
1634          {
1635          throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception );
1636          }
1637    
1638        Fields results = new Fields( fields );
1639    
1640        results.types = this.types == null ? new Type[ size() ] : this.types;
1641        results.types[ pos ] = type;
1642    
1643        return results;
1644        }
1645    
1646      /**
1647       * Method applyType should be used to associate {@link java.lang.reflect.Type} with a given field name or position
1648       * as declared in the given Fields parameter.
1649       * <p/>
1650       * A new instance of Fields will be returned, this instance will not be modified.
1651       * <p/>
1652       *
1653       * @param fields of type Fields
1654       */
1655      public Fields applyTypes( Fields fields )
1656        {
1657        Fields result = new Fields( this.fields, this.types );
1658    
1659        for( Comparable field : fields )
1660          result = result.applyType( field, fields.getType( fields.getPos( field ) ) );
1661    
1662        return result;
1663        }
1664    
1665      /**
1666       * Method applyTypes returns a new Fields instance with the given types, replacing any existing type
1667       * information within the new instance.
1668       * <p/>
1669       * The Class array must be the same length as the number for fields in this instance.
1670       *
1671       * @param types the class types of this Fields object.
1672       * @return returns a new instance of Fields with this instances field names and the given types
1673       */
1674      public Fields applyTypes( Type... types )
1675        {
1676        Fields result = new Fields( fields );
1677    
1678        if( types == null ) // allows for type erasure
1679          return result;
1680    
1681        if( types.length != size() )
1682          throw new IllegalArgumentException( "given number of class instances must match fields size" );
1683    
1684        for( Type type : types )
1685          {
1686          if( type == null )
1687            throw new IllegalArgumentException( "type must not be null" );
1688          }
1689    
1690        result.types = copyTypes( types, types.length ); // make copy as Class[] could be passed in
1691    
1692        return result;
1693        }
1694    
1695      /**
1696       * Returns the Type at the given position or having the fieldName.
1697       *
1698       * @param fieldName of type String or Number
1699       * @return the Type
1700       */
1701      public Type getType( Comparable fieldName )
1702        {
1703        if( !hasTypes() )
1704          return null;
1705    
1706        return getType( getPos( fieldName ) );
1707        }
1708    
1709      public Type getType( int pos )
1710        {
1711        if( !hasTypes() )
1712          return null;
1713    
1714        return this.types[ pos ];
1715        }
1716    
1717      /**
1718       * Returns the Class for the given position value.
1719       * <p/>
1720       * If the underlying value is of type {@link CoercibleType}, the result of
1721       * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned.
1722       *
1723       * @param fieldName of type String or Number
1724       * @return type Class
1725       */
1726      public Class getTypeClass( Comparable fieldName )
1727        {
1728        if( !hasTypes() )
1729          return null;
1730    
1731        return getTypeClass( getPos( fieldName ) );
1732        }
1733    
1734      public Class getTypeClass( int pos )
1735        {
1736        Type type = getType( pos );
1737    
1738        if( type instanceof CoercibleType )
1739          return ( (CoercibleType) type ).getCanonicalType();
1740    
1741        return (Class) type;
1742        }
1743    
1744      protected void setType( int pos, Type type )
1745        {
1746        if( type == null )
1747          throw new IllegalArgumentException( "type may not be null" );
1748    
1749        this.types[ pos ] = type;
1750        }
1751    
1752      /**
1753       * Returns a copy of the current types Type[] if any, else null.
1754       *
1755       * @return of type Type[]
1756       */
1757      public Type[] getTypes()
1758        {
1759        return copyTypes( types, size() );
1760        }
1761    
1762      /**
1763       * Returns a copy of the current types Class[] if any, else null.
1764       * <p/>
1765       * If any underlying value is of type {@link CoercibleType}, the result of
1766       * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned.
1767       *
1768       * @return of type Class
1769       */
1770      public Class[] getTypesClasses()
1771        {
1772        if( types == null )
1773          return null;
1774    
1775        Class[] classes = new Class[ types.length ];
1776    
1777        for( int i = 0; i < types.length; i++ )
1778          {
1779          if( types[ i ] instanceof CoercibleType )
1780            classes[ i ] = ( (CoercibleType) types[ i ] ).getCanonicalType();
1781          else
1782            classes[ i ] = (Class) types[ i ]; // this throws a more helpful exception vs arraycopy
1783          }
1784    
1785        return classes;
1786        }
1787    
1788      private static Type[] copyTypes( Type[] types, int size )
1789        {
1790        if( types == null )
1791          return null;
1792    
1793        Type[] copy = new Type[ size ];
1794    
1795        if( types.length != size )
1796          throw new IllegalArgumentException( "types array must be same size as fields array" );
1797    
1798        System.arraycopy( types, 0, copy, 0, size );
1799    
1800        return copy;
1801        }
1802    
1803      /**
1804       * Returns true if there are types associated with this instance.
1805       *
1806       * @return boolean
1807       */
1808      public final boolean hasTypes()
1809        {
1810        return types != null;
1811        }
1812    
1813      /**
1814       * Method setComparator should be used to associate a {@link java.util.Comparator} with a given field name or position.
1815       * <p/>
1816       * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
1817       * be considered.
1818       *
1819       * @param fieldName  of type Comparable
1820       * @param comparator of type Comparator
1821       */
1822      public void setComparator( Comparable fieldName, Comparator comparator )
1823        {
1824        if( !( comparator instanceof Serializable ) )
1825          throw new IllegalArgumentException( "given comparator must be serializable" );
1826    
1827        if( comparators == null )
1828          comparators = new Comparator[ size() ];
1829    
1830        try
1831          {
1832          comparators[ getPos( asFieldName( fieldName ) ) ] = comparator;
1833          }
1834        catch( FieldsResolverException exception )
1835          {
1836          throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception );
1837          }
1838        }
1839    
1840      /**
1841       * Method setComparators sets all the comparators of this Fields object. The Comparator array
1842       * must be the same length as the number for fields in this instance.
1843       *
1844       * @param comparators the comparators of this Fields object.
1845       */
1846      public void setComparators( Comparator... comparators )
1847        {
1848        if( comparators.length != size() )
1849          throw new IllegalArgumentException( "given number of comparator instances must match fields size" );
1850    
1851        for( Comparator comparator : comparators )
1852          {
1853          if( !( comparator instanceof Serializable ) )
1854            throw new IllegalArgumentException( "comparators must be serializable" );
1855          }
1856    
1857        this.comparators = comparators;
1858        }
1859    
1860      protected static Comparable asFieldName( Comparable fieldName )
1861        {
1862        if( fieldName instanceof Fields )
1863          {
1864          Fields fields = (Fields) fieldName;
1865    
1866          if( !fields.isDefined() )
1867            throw new TupleException( "given Fields instance must explicitly declare one field name or position: " + fields.printVerbose() );
1868    
1869          fieldName = fields.get( 0 );
1870          }
1871    
1872        return fieldName;
1873        }
1874    
1875      protected Comparator getComparator( Comparable fieldName )
1876        {
1877        if( comparators == null )
1878          return null;
1879    
1880        try
1881          {
1882          return comparators[ getPos( asFieldName( fieldName ) ) ];
1883          }
1884        catch( FieldsResolverException exception )
1885          {
1886          return null;
1887          }
1888        }
1889    
1890      /**
1891       * Method getComparators returns the comparators of this Fields object.
1892       *
1893       * @return the comparators (type Comparator[]) of this Fields object.
1894       */
1895      public Comparator[] getComparators()
1896        {
1897        Comparator[] copy = new Comparator[ size() ];
1898    
1899        if( comparators != null )
1900          System.arraycopy( comparators, 0, copy, 0, size() );
1901    
1902        return copy;
1903        }
1904    
1905      /**
1906       * Method hasComparators test if this Fields instance has Comparators.
1907       *
1908       * @return boolean
1909       */
1910      public boolean hasComparators()
1911        {
1912        return comparators != null;
1913        }
1914    
1915      @Override
1916      public int compare( Tuple lhs, Tuple rhs )
1917        {
1918        return lhs.compareTo( comparators, rhs );
1919        }
1920    
1921      @Override
1922      public boolean equals( Object object )
1923        {
1924        if( this == object )
1925          return true;
1926        if( object == null || getClass() != object.getClass() )
1927          return false;
1928    
1929        Fields fields = (Fields) object;
1930    
1931        return equalsFields( fields ) && Arrays.equals( types, fields.types );
1932        }
1933    
1934      /**
1935       * Method equalsFields compares only the internal field names and postions only between this and the given Fields
1936       * instance. Type information is ignored.
1937       *
1938       * @param fields of type int
1939       * @return true if this and the given instance have the same positions and/or field names.
1940       */
1941      public boolean equalsFields( Fields fields )
1942        {
1943        return fields != null && this.kind == fields.kind && Arrays.equals( get(), fields.get() );
1944        }
1945    
1946      @Override
1947      public int hashCode()
1948        {
1949        if( hashCode == 0 )
1950          hashCode = get() != null ? Arrays.hashCode( get() ) : 0;
1951    
1952        return hashCode;
1953        }
1954      }