    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      }