001    /*
002     * Copyright (c) 2007-2015 Concurrent, Inc. All Rights Reserved.
003     *
004     * Project and contact information: http://www.cascading.org/
005     *
006     * This file is part of the Cascading project.
007     *
008     * Licensed under the Apache License, Version 2.0 (the "License");
009     * you may not use this file except in compliance with the License.
010     * You may obtain a copy of the License at
011     *
012     *     http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing, software
015     * distributed under the License is distributed on an "AS IS" BASIS,
016     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017     * See the License for the specific language governing permissions and
018     * limitations under the License.
019     */
020    
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      /**
889       * Method hasRelativePos returns true if any ordinal position is relative (< 0)
890       *
891       * @return true if any ordinal position is relative (< 0)
892       */
893      public boolean hasRelativePos()
894        {
895        for( int i : getPos() )
896          {
897          if( i < 0 )
898            return true;
899          }
900    
901        return false;
902        }
903    
904      private int[] makeThisPos()
905        {
906        int[] pos = new int[ size() ];
907    
908        for( int i = 0; i < size(); i++ )
909          {
910          Comparable field = get( i );
911    
912          if( field instanceof Number )
913            pos[ i ] = (Integer) field;
914          else
915            pos[ i ] = i;
916          }
917    
918        return pos;
919        }
920    
921      private final Map<Fields, int[]> getPosCache()
922        {
923        if( posCache == null )
924          posCache = new HashMap<Fields, int[]>();
925    
926        return posCache;
927        }
928    
929      private final int[] putReturn( Fields fields, int[] pos )
930        {
931        getPosCache().put( fields, pos );
932    
933        return pos;
934        }
935    
936      public final int[] getPos( Fields fields )
937        {
938        return getPos( fields, -1 );
939        }
940    
941      final int[] getPos( Fields fields, int tupleSize )
942        {
943        // test for key, as we stuff a null value
944        if( !isUnknown() && getPosCache().containsKey( fields ) )
945          return getPosCache().get( fields );
946    
947        if( fields.isAll() )
948          return putReturn( fields, null ); // return null, not getPos()
949    
950        if( isAll() )
951          return putReturn( fields, fields.getPos() );
952    
953        // don't cache unknown
954        if( size() == 0 && isUnknown() )
955          return translatePos( fields, tupleSize );
956    
957        int[] pos = translatePos( fields, size() );
958    
959        return putReturn( fields, pos );
960        }
961    
962      private int[] translatePos( Fields fields, int fieldSize )
963        {
964        int[] pos = new int[ fields.size() ];
965    
966        for( int i = 0; i < fields.size(); i++ )
967          {
968          Comparable field = fields.get( i );
969    
970          if( field instanceof Number )
971            pos[ i ] = translatePos( (Integer) field, fieldSize );
972          else
973            pos[ i ] = indexOf( field );
974          }
975    
976        return pos;
977        }
978    
979      final int translatePos( Integer integer )
980        {
981        return translatePos( integer, size() );
982        }
983    
984      final int translatePos( Integer integer, int size )
985        {
986        if( size == -1 )
987          return integer;
988    
989        if( integer < 0 )
990          integer = size + integer;
991    
992        if( !isUnknown() && ( integer >= size || integer < 0 ) )
993          throw new TupleException( "position value is too large: " + integer + ", positions in field: " + size );
994    
995        return integer;
996        }
997    
998      /**
999       * Method getPos returns the index of the give field value in this Fields instance. The index corresponds to the
1000       * Tuple value index in an associated Tuple instance.
1001       *
1002       * @param fieldName of type Comparable
1003       * @return int
1004       */
1005      public int getPos( Comparable fieldName )
1006        {
1007        if( fieldName instanceof Number )
1008          return translatePos( (Integer) fieldName );
1009        else
1010          return indexOf( fieldName );
1011        }
1012    
1013      private final Map<Comparable, Integer> getIndex()
1014        {
1015        if( index != null )
1016          return index;
1017    
1018        // make thread-safe by not having invalid intermediate state
1019        Map<Comparable, Integer> local = new HashMap<Comparable, Integer>();
1020    
1021        for( int i = 0; i < size(); i++ )
1022          local.put( get( i ), i );
1023    
1024        return index = local;
1025        }
1026    
1027      private int indexOf( Comparable fieldName )
1028        {
1029        Integer result = getIndex().get( fieldName );
1030    
1031        if( result == null )
1032          throw new FieldsResolverException( this, new Fields( fieldName ) );
1033    
1034        return result;
1035        }
1036    
1037      int indexOfSafe( Comparable fieldName )
1038        {
1039        Integer result = getIndex().get( fieldName );
1040    
1041        if( result == null )
1042          return -1;
1043    
1044        return result;
1045        }
1046    
1047      /**
1048       * Method iterator return an unmodifiable iterator of field values. if {@link #isSubstitution()} returns true,
1049       * this iterator will be empty.
1050       *
1051       * @return Iterator
1052       */
1053      public Iterator iterator()
1054        {
1055        return Collections.unmodifiableList( Arrays.asList( fields ) ).iterator();
1056        }
1057    
1058      /**
1059       * Method select returns a new Fields instance with fields specified by the given selector.
1060       *
1061       * @param selector of type Fields
1062       * @return Fields
1063       */
1064      public Fields select( Fields selector )
1065        {
1066        if( !isOrdered() )
1067          throw new TupleException( "this fields instance can only be used as a selector" );
1068    
1069        if( selector.isAll() )
1070          return this;
1071    
1072        // supports -1_UNKNOWN_RETURNED
1073        // guarantees pos arguments remain selector positions, not absolute positions
1074        if( isUnknown() )
1075          return asSelector( selector );
1076    
1077        if( selector.isNone() )
1078          return NONE;
1079    
1080        Fields result = size( selector.size() );
1081    
1082        for( int i = 0; i < selector.size(); i++ )
1083          {
1084          Comparable field = selector.get( i );
1085    
1086          if( field instanceof String )
1087            {
1088            result.set( i, get( indexOf( field ) ) );
1089            continue;
1090            }
1091    
1092          int pos = translatePos( (Integer) field );
1093    
1094          if( this.get( pos ) instanceof String )
1095            result.set( i, this.get( pos ) );
1096          else
1097            result.set( i, pos ); // use absolute position if no field name
1098          }
1099    
1100        if( this.types != null )
1101          {
1102          result.types = new Type[ result.size() ];
1103    
1104          for( int i = 0; i < selector.size(); i++ )
1105            {
1106            Comparable field = selector.get( i );
1107    
1108            if( field instanceof String )
1109              result.setType( i, getType( indexOf( field ) ) );
1110            else
1111              result.setType( i, getType( translatePos( (Integer) field ) ) );
1112            }
1113          }
1114    
1115        return result;
1116        }
1117    
1118      /**
1119       * Method selectPos returns a Fields instance with only positional fields, no field names.
1120       *
1121       * @param selector of type Fields
1122       * @return Fields instance with only positions.
1123       */
1124      public Fields selectPos( Fields selector )
1125        {
1126        return selectPos( selector, 0 );
1127        }
1128    
1129      /**
1130       * Method selectPos returns a Fields instance with only positional fields, offset by given offset value, no field names.
1131       *
1132       * @param selector of type Fields
1133       * @param offset   of type int
1134       * @return Fields instance with only positions.
1135       */
1136      public Fields selectPos( Fields selector, int offset )
1137        {
1138        int[] pos = getPos( selector );
1139    
1140        Fields results = size( pos.length );
1141    
1142        for( int i = 0; i < pos.length; i++ )
1143          results.fields[ i ] = pos[ i ] + offset;
1144    
1145        return results;
1146        }
1147    
1148      /**
1149       * Method subtract returns the difference between this instance and the given fields instance.
1150       * <p/>
1151       * See {@link #append(Fields)} for adding field names.
1152       *
1153       * @param fields of type Fields
1154       * @return Fields
1155       */
1156      public Fields subtract( Fields fields )
1157        {
1158        if( fields.isAll() )
1159          return Fields.NONE;
1160    
1161        if( fields.isNone() )
1162          return this;
1163    
1164        List<Comparable> list = new LinkedList<Comparable>();
1165        Collections.addAll( list, this.get() );
1166        int[] pos = getPos( fields, -1 );
1167    
1168        for( int i : pos )
1169          list.set( i, null );
1170    
1171        Util.removeAllNulls( list );
1172    
1173        Type[] newTypes = null;
1174    
1175        if( this.types != null )
1176          {
1177          List<Type> types = new LinkedList<Type>();
1178          Collections.addAll( types, this.types );
1179    
1180          for( int i : pos )
1181            types.set( i, null );
1182    
1183          Util.removeAllNulls( types );
1184    
1185          newTypes = types.toArray( new Type[ types.size() ] );
1186          }
1187    
1188        return new Fields( list.toArray( new Comparable[ list.size() ] ), newTypes );
1189        }
1190    
1191      /**
1192       * Method is used for appending the given Fields instance to this instance, into a new Fields instance.
1193       * <p/>
1194       * See {@link #subtract(Fields)} for removing field names.
1195       * <p/>
1196       * This method has been deprecated, see {@link #join(Fields...)}
1197       *
1198       * @param fields of type Fields[]
1199       * @return Fields
1200       */
1201      @Deprecated
1202      public Fields append( Fields[] fields )
1203        {
1204        if( fields.length == 0 )
1205          return null;
1206    
1207        Fields field = this;
1208    
1209        for( Fields current : fields )
1210          field = field.append( current );
1211    
1212        return field;
1213        }
1214    
1215      /**
1216       * Method is used for appending the given Fields instance to this instance, into a new Fields instance suitable
1217       * for use as a field declaration.
1218       * <p/>
1219       * That is, any positional elements (including relative positions like {@code -1}, will be ignored during the
1220       * append. For example, the second {@code 0} position is lost in the result.
1221       * <p/>
1222       * {@code assert new Fields( 0, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 0, "a", 2, "b" )}
1223       * <p/>
1224       * See {@link #subtract(Fields)} for removing field names.
1225       *
1226       * @param fields of type Fields
1227       * @return Fields
1228       */
1229      public Fields append( Fields fields )
1230        {
1231        return appendInternal( fields, false );
1232        }
1233    
1234      /**
1235       * Method is used for appending the given Fields instance to this instance, into a new Fields instance
1236       * suitable for use as a field selector.
1237       * <p/>
1238       * That is, any positional elements will be retained during the append. For example, the {@code 5} and {@code 0}
1239       * are retained in the result.
1240       * <p/>
1241       * {@code assert new Fields( 5, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 5, "a", 0, "b" )}
1242       * <p/>
1243       * Note any relative positional elements are retained, thus appending two Fields each declaring {@code -1}
1244       * position will result in a TupleException noting duplicate fields.
1245       * <p/>
1246       * See {@link #subtract(Fields)} for removing field names.
1247       *
1248       * @param fields of type Fields
1249       * @return Fields
1250       */
1251      public Fields appendSelector( Fields fields )
1252        {
1253        return appendInternal( fields, true );
1254        }
1255    
1256      private Fields appendInternal( Fields fields, boolean isSelect )
1257        {
1258        if( fields == null )
1259          return this;
1260    
1261        // allow unordered fields to be appended to build more complex selectors
1262        if( this.isAll() || fields.isAll() )
1263          throw new TupleException( "cannot append fields: " + this.print() + " + " + fields.print() );
1264    
1265        if( ( this.isUnknown() || this.size() == 0 ) && fields.isUnknown() )
1266          return UNKNOWN;
1267    
1268        if( fields.isNone() )
1269          return this;
1270    
1271        if( this.isNone() )
1272          return fields;
1273    
1274        Set<Comparable> names = new HashSet<Comparable>();
1275    
1276        // init the Field
1277        Fields result = size( this.size() + fields.size() );
1278    
1279        // copy over field names from this side
1280        copyRetain( names, result, this, 0, isSelect );
1281        // copy over field names from that side
1282        copyRetain( names, result, fields, this.size(), isSelect );
1283    
1284        if( this.isUnknown() || fields.isUnknown() )
1285          result.kind = Kind.UNKNOWN;
1286    
1287        if( ( this.isNone() || this.types != null ) && fields.types != null )
1288          {
1289          result.types = new Type[ this.size() + fields.size() ];
1290    
1291          if( this.types != null ) // supports appending to NONE
1292            System.arraycopy( this.types, 0, result.types, 0, this.size() );
1293    
1294          System.arraycopy( fields.types, 0, result.types, this.size(), fields.size() );
1295          }
1296    
1297        return result;
1298        }
1299    
1300      /**
1301       * Method rename will rename the from fields to the values in to to fields. Fields may contain field names, or
1302       * positions.
1303       * <p/>
1304       * Using positions is useful to remove a field name put keep its place in the Tuple stream.
1305       *
1306       * @param from of type Fields
1307       * @param to   of type Fields
1308       * @return Fields
1309       */
1310      public Fields rename( Fields from, Fields to )
1311        {
1312        if( this.isSubstitution() || this.isUnknown() )
1313          throw new TupleException( "cannot rename fields in a substitution or unknown Fields instance: " + this.print() );
1314    
1315        if( from.size() != to.size() )
1316          throw new TupleException( "from and to fields must be the same size" );
1317    
1318        if( from.isSubstitution() || from.isUnknown() )
1319          throw new TupleException( "from fields may not be a substitution or unknown" );
1320    
1321        if( to.isSubstitution() || to.isUnknown() )
1322          throw new TupleException( "to fields may not be a substitution or unknown" );
1323    
1324        Comparable[] newFields = Arrays.copyOf( this.fields, this.fields.length );
1325    
1326        int[] pos = getPos( from );
1327    
1328        for( int i = 0; i < pos.length; i++ )
1329          newFields[ pos[ i ] ] = to.fields[ i ];
1330    
1331        Type[] newTypes = null;
1332    
1333        if( this.types != null && to.types != null )
1334          {
1335          newTypes = copyTypes( this.types, this.size() );
1336    
1337          for( int i = 0; i < pos.length; i++ )
1338            newTypes[ pos[ i ] ] = to.types[ i ];
1339          }
1340    
1341        return new Fields( newFields, newTypes );
1342        }
1343    
1344      /**
1345       * Method project will return a new Fields instance similar to the given fields instance
1346       * except any absolute positional elements will be replaced by the current field names, if any.
1347       *
1348       * @param fields of type Fields
1349       * @return Fields
1350       */
1351      public Fields project( Fields fields )
1352        {
1353        if( fields == null )
1354          return this;
1355    
1356        Fields results = size( fields.size() ).applyTypes( fields.getTypes() );
1357    
1358        for( int i = 0; i < fields.fields.length; i++ )
1359          {
1360          if( fields.fields[ i ] instanceof String )
1361            results.fields[ i ] = fields.fields[ i ];
1362          else if( this.fields[ i ] instanceof String )
1363            results.fields[ i ] = this.fields[ i ];
1364          else
1365            results.fields[ i ] = i;
1366          }
1367    
1368        return results;
1369        }
1370    
1371      private static void copy( Set<String> names, Fields result, Fields fields, int offset )
1372        {
1373        for( int i = 0; i < fields.size(); i++ )
1374          {
1375          Comparable field = fields.get( i );
1376    
1377          if( !( field instanceof String ) )
1378            continue;
1379    
1380          if( names != null )
1381            {
1382            if( names.contains( field ) )
1383              throw new TupleException( "field name already exists: " + field );
1384    
1385            names.add( (String) field );
1386            }
1387    
1388          result.set( i + offset, field );
1389          }
1390        }
1391    
1392      /**
1393       * Retains any relative positional elements like -1, but checks for duplicates
1394       *
1395       * @param names
1396       * @param result
1397       * @param fields
1398       * @param offset
1399       * @param isSelect
1400       */
1401      private static void copyRetain( Set<Comparable> names, Fields result, Fields fields, int offset, boolean isSelect )
1402        {
1403        for( int i = 0; i < fields.size(); i++ )
1404          {
1405          Comparable field = fields.get( i );
1406    
1407          if( !isSelect && field instanceof Integer )
1408            continue;
1409    
1410          if( names != null )
1411            {
1412            if( names.contains( field ) )
1413              throw new TupleException( "field name already exists: " + field );
1414    
1415            names.add( field );
1416            }
1417    
1418          result.set( i + offset, field );
1419          }
1420        }
1421    
1422      /**
1423       * Method verifyContains tests if this instance contains the field names and positions specified in the given
1424       * fields instance. If the test fails, a {@link TupleException} is thrown.
1425       *
1426       * @param fields of type Fields
1427       * @throws TupleException when one or more fields are not contained in this instance.
1428       */
1429      public void verifyContains( Fields fields )
1430        {
1431        if( isUnknown() )
1432          return;
1433    
1434        try
1435          {
1436          getPos( fields );
1437          }
1438        catch( TupleException exception )
1439          {
1440          throw new TupleException( "these fields " + print() + ", do not contain " + fields.print() );
1441          }
1442        }
1443    
1444      /**
1445       * Method contains returns true if this instance contains the field names and positions specified in the given
1446       * fields instance.
1447       *
1448       * @param fields of type Fields
1449       * @return boolean
1450       */
1451      public boolean contains( Fields fields )
1452        {
1453        try
1454          {
1455          getPos( fields );
1456          return true;
1457          }
1458        catch( Exception exception )
1459          {
1460          return false;
1461          }
1462        }
1463    
1464      /**
1465       * Method compareTo compares this instance to the given Fields instance.
1466       *
1467       * @param other of type Fields
1468       * @return int
1469       */
1470      public int compareTo( Fields other )
1471        {
1472        if( other.size() != size() )
1473          return other.size() < size() ? 1 : -1;
1474    
1475        for( int i = 0; i < size(); i++ )
1476          {
1477          int c = get( i ).compareTo( other.get( i ) );
1478    
1479          if( c != 0 )
1480            return c;
1481          }
1482    
1483        return 0;
1484        }
1485    
1486      /**
1487       * Method compareTo implements {@link Comparable#compareTo(Object)}.
1488       *
1489       * @param other of type Object
1490       * @return int
1491       */
1492      public int compareTo( Object other )
1493        {
1494        if( other instanceof Fields )
1495          return compareTo( (Fields) other );
1496        else
1497          return -1;
1498        }
1499    
1500      /**
1501       * Method print returns a String representation of this instance.
1502       *
1503       * @return String
1504       */
1505      public String print()
1506        {
1507        return "[" + toString() + "]";
1508        }
1509    
1510      /**
1511       * Method printLong returns a String representation of this instance along with the size.
1512       *
1513       * @return String
1514       */
1515      public String printVerbose()
1516        {
1517        String fieldsString = toString();
1518    
1519        return "[{" + ( isDefined() ? size() : "?" ) + "}:" + fieldsString + "]";
1520        }
1521    
1522      @Override
1523      public String toString()
1524        {
1525        String string;
1526    
1527        if( isOrdered() )
1528          string = orderedToString();
1529        else
1530          string = unorderedToString();
1531    
1532        if( types != null )
1533          string += " | " + Util.join( Util.simpleTypeNames( types ), ", " );
1534    
1535        return string;
1536        }
1537    
1538      private String orderedToString()
1539        {
1540        StringBuffer buffer = new StringBuffer();
1541    
1542        if( size() != 0 )
1543          {
1544          int startIndex = get( 0 ) instanceof Number ? (Integer) get( 0 ) : 0;
1545    
1546          for( int i = 0; i < size(); i++ )
1547            {
1548            Comparable field = get( i );
1549    
1550            if( field instanceof Number )
1551              {
1552              if( i + 1 == size() || !( get( i + 1 ) instanceof Number ) )
1553                {
1554                if( buffer.length() != 0 )
1555                  buffer.append( ", " );
1556    
1557                if( startIndex != i )
1558                  buffer.append( startIndex ).append( ":" ).append( field );
1559                else
1560                  buffer.append( i );
1561    
1562                startIndex = i;
1563                }
1564    
1565              continue;
1566              }
1567    
1568            if( i != 0 )
1569              buffer.append( ", " );
1570    
1571            if( field instanceof String )
1572              buffer.append( "\'" ).append( field ).append( "\'" );
1573            else if( field instanceof Fields )
1574              buffer.append( ( (Fields) field ).print() );
1575    
1576            startIndex = i + 1;
1577            }
1578          }
1579    
1580        if( kind != null )
1581          {
1582          if( buffer.length() != 0 )
1583            buffer.append( ", " );
1584          buffer.append( kind );
1585          }
1586    
1587        return buffer.toString();
1588        }
1589    
1590      private String unorderedToString()
1591        {
1592        StringBuffer buffer = new StringBuffer();
1593    
1594        for( Object field : get() )
1595          {
1596          if( buffer.length() != 0 )
1597            buffer.append( ", " );
1598    
1599          if( field instanceof String )
1600            buffer.append( "\'" ).append( field ).append( "\'" );
1601          else if( field instanceof Fields )
1602            buffer.append( ( (Fields) field ).print() );
1603          else
1604            buffer.append( field );
1605          }
1606    
1607        if( kind != null )
1608          {
1609          if( buffer.length() != 0 )
1610            buffer.append( ", " );
1611          buffer.append( kind );
1612          }
1613    
1614        return buffer.toString();
1615        }
1616    
1617      /**
1618       * Method size returns the number of field positions in this instance.
1619       *
1620       * @return int
1621       */
1622      public final int size()
1623        {
1624        return fields.length;
1625        }
1626    
1627      /**
1628       * Method applyType should be used to associate a {@link java.lang.reflect.Type} with a given field name or position.
1629       * A new instance of Fields will be returned, this instance will not be modified.
1630       * <p/>
1631       * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
1632       * be considered.
1633       *
1634       * @param fieldName of type Comparable
1635       * @param type      of type Type
1636       */
1637      public Fields applyType( Comparable fieldName, Type type )
1638        {
1639        if( type == null )
1640          throw new IllegalArgumentException( "given type must not be null" );
1641    
1642        int pos;
1643    
1644        try
1645          {
1646          pos = getPos( asFieldName( fieldName ) );
1647          }
1648        catch( FieldsResolverException exception )
1649          {
1650          throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception );
1651          }
1652    
1653        Fields results = new Fields( fields );
1654    
1655        results.types = this.types == null ? new Type[ size() ] : this.types;
1656        results.types[ pos ] = type;
1657    
1658        return results;
1659        }
1660    
1661      /**
1662       * Method applyType should be used to associate {@link java.lang.reflect.Type} with a given field name or position
1663       * as declared in the given Fields parameter.
1664       * <p/>
1665       * A new instance of Fields will be returned, this instance will not be modified.
1666       * <p/>
1667       *
1668       * @param fields of type Fields
1669       */
1670      public Fields applyTypes( Fields fields )
1671        {
1672        Fields result = new Fields( this.fields, this.types );
1673    
1674        for( Comparable field : fields )
1675          result = result.applyType( field, fields.getType( fields.getPos( field ) ) );
1676    
1677        return result;
1678        }
1679    
1680      /**
1681       * Method applyTypes returns a new Fields instance with the given types, replacing any existing type
1682       * information within the new instance.
1683       * <p/>
1684       * The Class array must be the same length as the number for fields in this instance.
1685       *
1686       * @param types the class types of this Fields object.
1687       * @return returns a new instance of Fields with this instances field names and the given types
1688       */
1689      public Fields applyTypes( Type... types )
1690        {
1691        Fields result = new Fields( fields );
1692    
1693        if( types == null ) // allows for type erasure
1694          return result;
1695    
1696        if( types.length != size() )
1697          throw new IllegalArgumentException( "given number of class instances must match fields size" );
1698    
1699        for( Type type : types )
1700          {
1701          if( type == null )
1702            throw new IllegalArgumentException( "type must not be null" );
1703          }
1704    
1705        result.types = copyTypes( types, types.length ); // make copy as Class[] could be passed in
1706    
1707        return result;
1708        }
1709    
1710      /**
1711       * Returns the Type at the given position or having the fieldName.
1712       *
1713       * @param fieldName of type String or Number
1714       * @return the Type
1715       */
1716      public Type getType( Comparable fieldName )
1717        {
1718        if( !hasTypes() )
1719          return null;
1720    
1721        return getType( getPos( fieldName ) );
1722        }
1723    
1724      public Type getType( int pos )
1725        {
1726        if( !hasTypes() )
1727          return null;
1728    
1729        return this.types[ pos ];
1730        }
1731    
1732      /**
1733       * Returns the Class for the given position value.
1734       * <p/>
1735       * If the underlying value is of type {@link CoercibleType}, the result of
1736       * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned.
1737       *
1738       * @param fieldName of type String or Number
1739       * @return type Class
1740       */
1741      public Class getTypeClass( Comparable fieldName )
1742        {
1743        if( !hasTypes() )
1744          return null;
1745    
1746        return getTypeClass( getPos( fieldName ) );
1747        }
1748    
1749      public Class getTypeClass( int pos )
1750        {
1751        Type type = getType( pos );
1752    
1753        if( type instanceof CoercibleType )
1754          return ( (CoercibleType) type ).getCanonicalType();
1755    
1756        return (Class) type;
1757        }
1758    
1759      protected void setType( int pos, Type type )
1760        {
1761        if( type == null )
1762          throw new IllegalArgumentException( "type may not be null" );
1763    
1764        this.types[ pos ] = type;
1765        }
1766    
1767      /**
1768       * Returns a copy of the current types Type[] if any, else null.
1769       *
1770       * @return of type Type[]
1771       */
1772      public Type[] getTypes()
1773        {
1774        return copyTypes( types, size() );
1775        }
1776    
1777      /**
1778       * Returns a copy of the current types Class[] if any, else null.
1779       * <p/>
1780       * If any underlying value is of type {@link CoercibleType}, the result of
1781       * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned.
1782       *
1783       * @return of type Class
1784       */
1785      public Class[] getTypesClasses()
1786        {
1787        if( types == null )
1788          return null;
1789    
1790        Class[] classes = new Class[ types.length ];
1791    
1792        for( int i = 0; i < types.length; i++ )
1793          {
1794          if( types[ i ] instanceof CoercibleType )
1795            classes[ i ] = ( (CoercibleType) types[ i ] ).getCanonicalType();
1796          else
1797            classes[ i ] = (Class) types[ i ]; // this throws a more helpful exception vs arraycopy
1798          }
1799    
1800        return classes;
1801        }
1802    
1803      private static Type[] copyTypes( Type[] types, int size )
1804        {
1805        if( types == null )
1806          return null;
1807    
1808        Type[] copy = new Type[ size ];
1809    
1810        if( types.length != size )
1811          throw new IllegalArgumentException( "types array must be same size as fields array" );
1812    
1813        System.arraycopy( types, 0, copy, 0, size );
1814    
1815        return copy;
1816        }
1817    
1818      /**
1819       * Returns true if there are types associated with this instance.
1820       *
1821       * @return boolean
1822       */
1823      public final boolean hasTypes()
1824        {
1825        return types != null;
1826        }
1827    
1828      /**
1829       * Method setComparator should be used to associate a {@link java.util.Comparator} with a given field name or position.
1830       * <p/>
1831       * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
1832       * be considered.
1833       *
1834       * @param fieldName  of type Comparable
1835       * @param comparator of type Comparator
1836       */
1837      public void setComparator( Comparable fieldName, Comparator comparator )
1838        {
1839        if( !( comparator instanceof Serializable ) )
1840          throw new IllegalArgumentException( "given comparator must be serializable" );
1841    
1842        if( comparators == null )
1843          comparators = new Comparator[ size() ];
1844    
1845        try
1846          {
1847          comparators[ getPos( asFieldName( fieldName ) ) ] = comparator;
1848          }
1849        catch( FieldsResolverException exception )
1850          {
1851          throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception );
1852          }
1853        }
1854    
1855      /**
1856       * Method setComparators sets all the comparators of this Fields object. The Comparator array
1857       * must be the same length as the number for fields in this instance.
1858       *
1859       * @param comparators the comparators of this Fields object.
1860       */
1861      public void setComparators( Comparator... comparators )
1862        {
1863        if( comparators.length != size() )
1864          throw new IllegalArgumentException( "given number of comparator instances must match fields size" );
1865    
1866        for( Comparator comparator : comparators )
1867          {
1868          if( !( comparator instanceof Serializable ) )
1869            throw new IllegalArgumentException( "comparators must be serializable" );
1870          }
1871    
1872        this.comparators = comparators;
1873        }
1874    
1875      protected static Comparable asFieldName( Comparable fieldName )
1876        {
1877        if( fieldName instanceof Fields )
1878          {
1879          Fields fields = (Fields) fieldName;
1880    
1881          if( !fields.isDefined() )
1882            throw new TupleException( "given Fields instance must explicitly declare one field name or position: " + fields.printVerbose() );
1883    
1884          fieldName = fields.get( 0 );
1885          }
1886    
1887        return fieldName;
1888        }
1889    
1890      protected Comparator getComparator( Comparable fieldName )
1891        {
1892        if( comparators == null )
1893          return null;
1894    
1895        try
1896          {
1897          return comparators[ getPos( asFieldName( fieldName ) ) ];
1898          }
1899        catch( FieldsResolverException exception )
1900          {
1901          return null;
1902          }
1903        }
1904    
1905      /**
1906       * Method getComparators returns the comparators of this Fields object.
1907       *
1908       * @return the comparators (type Comparator[]) of this Fields object.
1909       */
1910      public Comparator[] getComparators()
1911        {
1912        Comparator[] copy = new Comparator[ size() ];
1913    
1914        if( comparators != null )
1915          System.arraycopy( comparators, 0, copy, 0, size() );
1916    
1917        return copy;
1918        }
1919    
1920      /**
1921       * Method hasComparators test if this Fields instance has Comparators.
1922       *
1923       * @return boolean
1924       */
1925      public boolean hasComparators()
1926        {
1927        return comparators != null;
1928        }
1929    
1930      @Override
1931      public int compare( Tuple lhs, Tuple rhs )
1932        {
1933        return lhs.compareTo( comparators, rhs );
1934        }
1935    
1936      @Override
1937      public boolean equals( Object object )
1938        {
1939        if( this == object )
1940          return true;
1941        if( object == null || getClass() != object.getClass() )
1942          return false;
1943    
1944        Fields fields = (Fields) object;
1945    
1946        return equalsFields( fields ) && Arrays.equals( types, fields.types );
1947        }
1948    
1949      /**
1950       * Method equalsFields compares only the internal field names and postions only between this and the given Fields
1951       * instance. Type information is ignored.
1952       *
1953       * @param fields of type int
1954       * @return true if this and the given instance have the same positions and/or field names.
1955       */
1956      public boolean equalsFields( Fields fields )
1957        {
1958        return fields != null && this.kind == fields.kind && Arrays.equals( get(), fields.get() );
1959        }
1960    
1961      @Override
1962      public int hashCode()
1963        {
1964        if( hashCode == 0 )
1965          hashCode = get() != null ? Arrays.hashCode( get() ) : 0;
1966    
1967        return hashCode;
1968        }
1969      }