001/*
002 * Copyright (c) 2016-2018 Chris K Wensel <chris@wensel.net>. All Rights Reserved.
003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
004 *
005 * Project and contact information: http://www.cascading.org/
006 *
007 * This file is part of the Cascading project.
008 *
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package cascading.tuple;
023
024import java.beans.ConstructorProperties;
025import java.lang.reflect.Type;
026import java.util.Arrays;
027import java.util.Iterator;
028
029import cascading.tuple.coerce.Coercions;
030import cascading.tuple.type.CoercibleType;
031import cascading.util.ForeverValueIterator;
032
033/**
034 * Class TupleEntry allows a {@link Tuple} instance and its declaring {@link Fields} instance to be used as a single object.
035 * <p>
036 * Once a TupleEntry is created, its Fields cannot be changed, but the Tuple instance it holds can be replaced or
037 * modified. The managed Tuple should not have elements added or removed, as this will break the relationship with
038 * the associated Fields instance.
039 * <p>
040 * If type information is provided on the Fields instance, all setters on this class will use that information to
041 * coerce the given object to the expected type.
042 * <p>
043 * For example, if position is is of type {@code long}, then {@code entry.setString(0, "9" )} will coerce the "9" to a
044 * long {@code 9}. Thus, {@code entry.getObject(0) == 9l}.
045 * <p>
046 * No coercion is performed with the {@link #getObject(Comparable)} and {@link #getObject(int)} methods.
047 * <p>
048 * To set a value without coercion, see the {@link #setRaw(Comparable, Object)} and {@link #setRaw(int, Object)}
049 * methods.
050 *
051 * @see Fields
052 * @see Tuple
053 */
054public class TupleEntry
055  {
056  private static final CoercibleType[] EMPTY_COERCIONS = new CoercibleType[ 0 ];
057  private static final ForeverValueIterator<CoercibleType> OBJECT_ITERATOR = new ForeverValueIterator<CoercibleType>( Coercions.OBJECT );
058
059  /** An EMPTY TupleEntry instance for use as a stand in instead of a {@code null}. */
060  public static final TupleEntry NULL = new TupleEntry( Fields.NONE, Tuple.NULL );
061
062  /** Field fields */
063  private Fields fields;
064
065  private CoercibleType[] coercions = EMPTY_COERCIONS;
066
067  /** Field isUnmodifiable */
068  private boolean isUnmodifiable = false;
069  /** Field tuple */
070  Tuple tuple;
071
072  /**
073   * Method select will select a new Tuple instance from the given set of entries. Entries order is significant to
074   * the selector.
075   *
076   * @param selector of type Fields
077   * @param entries  of type TupleEntry
078   * @return Tuple
079   */
080  public static Tuple select( Fields selector, TupleEntry... entries )
081    {
082    // todo: consider just appending tuples values and just peeking those values
083    Tuple result = null;
084
085    // does not do field checks
086    if( selector.isAll() )
087      {
088      for( TupleEntry entry : entries )
089        {
090        if( result == null )
091          result = entry.getTuple();
092        else
093          result = result.append( entry.getTuple() );
094        }
095
096      return result;
097      }
098
099    int size = 0;
100
101    for( TupleEntry entry : entries )
102      size += entry.size();
103
104    result = Tuple.size( selector.size() );
105
106    int offset = 0;
107
108    for( TupleEntry entry : entries )
109      {
110      for( int i = 0; i < selector.size(); i++ )
111        {
112        Comparable field = selector.get( i );
113
114        int pos;
115
116        if( field instanceof String )
117          {
118          pos = entry.fields.indexOfSafe( field );
119
120          if( pos == -1 )
121            continue;
122          }
123        else
124          {
125          pos = entry.fields.translatePos( (Integer) field, size ) - offset;
126
127          if( pos >= entry.size() || pos < 0 )
128            continue;
129          }
130
131        result.set( i, entry.getObject( pos ) ); // last in wins
132        }
133
134      offset += entry.size();
135      }
136
137    return result;
138    }
139
140  /** Constructor TupleEntry creates a new TupleEntry instance. */
141  public TupleEntry()
142    {
143    this.fields = Fields.NONE;
144
145    setCoercions();
146    }
147
148  /**
149   * Constructor TupleEntry creates a new TupleEntry instance.
150   *
151   * @param isUnmodifiable of type boolean
152   */
153  @ConstructorProperties({"isUnmodifiable"})
154  public TupleEntry( boolean isUnmodifiable )
155    {
156    this.fields = Fields.NONE;
157    this.isUnmodifiable = isUnmodifiable;
158
159    setCoercions();
160    }
161
162  /**
163   * Constructor TupleEntry creates a new TupleEntry instance.
164   *
165   * @param fields of type Fields
166   */
167  @ConstructorProperties({"fields"})
168  public TupleEntry( Fields fields )
169    {
170    if( fields == null )
171      throw new IllegalArgumentException( "fields may not be null" );
172
173    this.fields = fields;
174
175    setCoercions();
176    }
177
178  /**
179   * Constructor TupleEntry creates a new TupleEntry instance.
180   *
181   * @param fields         of type Fields
182   * @param isUnmodifiable of type boolean
183   */
184  @ConstructorProperties({"fields", "isUnmodifiable"})
185  public TupleEntry( Fields fields, boolean isUnmodifiable )
186    {
187    if( fields == null )
188      throw new IllegalArgumentException( "fields may not be null" );
189
190    this.fields = fields;
191    this.isUnmodifiable = isUnmodifiable;
192
193    setCoercions();
194    }
195
196  /**
197   * Constructor TupleEntry creates a new TupleEntry instance.
198   *
199   * @param fields         of type Fields
200   * @param tuple          of type Tuple
201   * @param isUnmodifiable of type boolean
202   */
203  @ConstructorProperties({"fields", "tuple", "isUnmodifiable"})
204  public TupleEntry( Fields fields, Tuple tuple, boolean isUnmodifiable )
205    {
206    if( fields == null )
207      throw new IllegalArgumentException( "fields may not be null" );
208
209    this.fields = fields;
210    this.isUnmodifiable = isUnmodifiable;
211    setTuple( tuple );
212
213    setCoercions();
214    }
215
216  /**
217   * Constructor TupleEntry creates a new TupleEntry instance.
218   *
219   * @param fields of type Fields
220   * @param tuple  of type Tuple
221   */
222  @ConstructorProperties({"fields", "tuple"})
223  public TupleEntry( Fields fields, Tuple tuple )
224    {
225    if( fields == null )
226      throw new IllegalArgumentException( "fields may not be null" );
227
228    this.fields = fields;
229    this.tuple = tuple;
230
231    setCoercions();
232    }
233
234  /**
235   * Constructor TupleEntry creates a new TupleEntry instance that is a safe copy of the given tupleEntry.
236   * <p>
237   * The new instance is safe to cache and will be modifiable regardless of the given tupleEntry state.
238   *
239   * @param tupleEntry of type TupleEntry
240   */
241  @ConstructorProperties({"tupleEntry"})
242  public TupleEntry( TupleEntry tupleEntry )
243    {
244    if( tupleEntry == null )
245      throw new IllegalArgumentException( "tupleEntry may not be null" );
246
247    this.fields = tupleEntry.getFields();
248    this.tuple = tupleEntry.getTupleCopy();
249
250    setCoercions();
251    }
252
253  /**
254   * Constructor TupleEntry creates a new TupleEntry instance.
255   *
256   * @param tuple of type Tuple
257   */
258  @ConstructorProperties({"tuple"})
259  public TupleEntry( Tuple tuple )
260    {
261    if( tuple == null )
262      throw new IllegalArgumentException( "tuple may not be null" );
263
264    this.fields = Fields.size( tuple.size() );
265    this.tuple = tuple;
266
267    setCoercions();
268    }
269
270  private void setCoercions()
271    {
272    if( coercions != EMPTY_COERCIONS )
273      return;
274
275    coercions = getCoercions( getFields(), tuple );
276    }
277
278  static CoercibleType[] getCoercions( Fields fields, Tuple tuple )
279    {
280    Type[] types = fields.types; // safe to not get a copy
281    int size = fields.size();
282
283    size = size == 0 && tuple != null ? tuple.size() : size;
284
285    if( size == 0 )
286      return EMPTY_COERCIONS;
287
288    return Coercions.coercibleArray( size, types );
289    }
290
291  /**
292   * Method isUnmodifiable returns true if this TupleEntry is unmodifiable.
293   *
294   * @return boolean
295   */
296  public boolean isUnmodifiable()
297    {
298    return isUnmodifiable;
299    }
300
301  /**
302   * Method getFields returns the fields of this TupleEntry object.
303   *
304   * @return the fields (type Fields) of this TupleEntry object.
305   */
306  public Fields getFields()
307    {
308    return fields;
309    }
310
311  /**
312   * Returns true if there are types associated with this instance.
313   *
314   * @return boolean
315   */
316  public boolean hasTypes()
317    {
318    return fields.hasTypes();
319    }
320
321  /**
322   * Method getTuple returns the tuple of this TupleEntry object.
323   *
324   * @return the tuple (type Tuple) of this TupleEntry object.
325   */
326  public Tuple getTuple()
327    {
328    return tuple;
329    }
330
331  /**
332   * Method getTupleCopy returns a copy of the tuple of this TupleEntry object.
333   *
334   * @return a copy of the tuple (type Tuple) of this TupleEntry object.
335   */
336  public Tuple getTupleCopy()
337    {
338    return new Tuple( tuple );
339    }
340
341  /**
342   * Method getCoercedTuple is a helper method for copying the current tuple elements into a new Tuple,
343   * of the same size, as the requested coerced types.
344   *
345   * @param types of type Type[]
346   * @return returns the a new Tuple instance with coerced values
347   */
348  public Tuple getCoercedTuple( Type[] types )
349    {
350    return getCoercedTuple( types, Tuple.size( types.length ) );
351    }
352
353  /**
354   * Method getCoercedTuple is a helper method for copying the current tuple elements into the new Tuple,
355   * of the same size, as the requested coerced types.
356   *
357   * @param types of type Type[]
358   * @param into  of type Tuple
359   * @return returns the given into Tuple instance with coerced values
360   */
361  public Tuple getCoercedTuple( Type[] types, Tuple into )
362    {
363    if( into == null )
364      throw new IllegalArgumentException( "into argument Tuple may not be null" );
365
366    if( coercions.length != types.length || types.length != into.size() )
367      throw new IllegalArgumentException( "current entry and given tuple and types must be same length" );
368
369    for( int i = 0; i < coercions.length; i++ )
370      {
371      Object element = tuple.getObject( i );
372      into.set( i, Coercions.coerce( coercions[ i ], element, types[ i ] ) );
373      }
374
375    return into;
376    }
377
378  /**
379   * Method setTuple sets the tuple of this TupleEntry object, no copy will be performed.
380   * <p>
381   * If the given tuple is "unmodifiable" ({@code Tuple.isUnmodifiable() == true}) and this TupleEntry is
382   * not "unmodifiable", an exception will be thrown.
383   * <p>
384   * Unmodifiable tuples are generally owned by the system and cannot be be changed and must not be cached.
385   *
386   * @param tuple the tuple of this TupleEntry object.
387   */
388  public void setTuple( Tuple tuple )
389    {
390    if( !isUnmodifiable && tuple != null && tuple.isUnmodifiable() )
391      throw new IllegalArgumentException( "current entry is modifiable but given tuple is not modifiable, make copy of given Tuple first" );
392
393    if( tuple != null && isUnmodifiable )
394      this.tuple = Tuples.asUnmodifiable( tuple );
395    else
396      this.tuple = tuple;
397
398    setCoercions();
399    }
400
401  /**
402   * Method setCanonicalTuple replaces each value of the current tuple with the given tuple elements after
403   * they are coerced.
404   * <p>
405   * This method will modify the existing Tuple wrapped by this TupleEntry instance even
406   * if it is marked as unmodifiable.
407   * <p>
408   * If tuple argument is {@code null}, the current tuple will be set to {@code null}.
409   *
410   * @param tuple to replace the current wrapped Tuple instance
411   */
412  public void setCanonicalTuple( Tuple tuple )
413    {
414    if( tuple == null )
415      {
416      this.tuple = null;
417      return;
418      }
419
420    if( isUnmodifiable )
421      tuple = Tuples.asUnmodifiable( tuple );
422
423    if( fields.size() != tuple.size() )
424      throw new IllegalArgumentException( "current entry and given tuple must be same length" );
425
426    for( int i = 0; i < coercions.length; i++ )
427      {
428      Object element = tuple.getObject( i );
429
430      this.tuple.set( i, coercions[ i ].canonical( element ) ); // force read type to the expected type
431      }
432    }
433
434  /**
435   * Method setCanonicalValues replaces each value of the current tuple with th give Object[]
436   * after they are coerced.
437   *
438   * @param values to replace the current wrapped tuple instance values
439   */
440  public void setCanonicalValues( Object[] values )
441    {
442    setCanonicalValues( values, 0, values.length );
443    }
444
445  /**
446   * Method setCanonicalValues replaces each value of the current tuple with th give Object[]
447   * after they are coerced.
448   *
449   * @param values to replace the current wrapped tuple instance values
450   * @param offset index to being the copy
451   * @param length the number of elements to copy
452   */
453  public void setCanonicalValues( Object[] values, int offset, int length )
454    {
455    if( fields.size() != length )
456      throw new IllegalArgumentException( "current entry and given array must be same length" );
457
458    for( int i = offset; i < coercions.length; i++ )
459      {
460      Object element = values[ i ];
461
462      this.tuple.set( i, coercions[ i ].canonical( element ) ); // force read type to the expected type
463      }
464    }
465
466  /**
467   * Method size returns the number of values in this instance.
468   *
469   * @return int
470   */
471  public int size()
472    {
473    return tuple.size();
474    }
475
476  /**
477   * Method getObject returns the value in the given position pos.
478   * <p>
479   * No coercion is performed if there is an associated coercible type.
480   *
481   * @param pos position of the element to return.
482   * @return Object
483   */
484  public Object getObject( int pos )
485    {
486    return tuple.getObject( pos );
487    }
488
489  /**
490   * Method getObject returns the value in the given field or position as the requested type.
491   * <p>
492   * Coercion is performed to the given type.
493   *
494   * @param pos position of the element to return.
495   * @return Object
496   */
497  public Object getObject( int pos, Type type )
498    {
499    if( pos > coercions.length - 1 )
500      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
501
502    return Coercions.coerce( coercions[ pos ], tuple.getObject( pos ), type );
503    }
504
505  /**
506   * Method getObject returns the value in the given field or position.
507   * <br>
508   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
509   * be considered.
510   * <p>
511   * No coercion is performed if there is an associated coercible type.
512   *
513   * @param fieldName field name or position to return
514   * @return Comparable
515   */
516  public Object getObject( Comparable fieldName )
517    {
518    int pos = fields.getPos( asFieldName( fieldName ) );
519    return tuple.getObject( pos );
520    }
521
522  /**
523   * Method getObject returns the value in the given field or position as the requested type.
524   * <br>
525   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
526   * be considered.
527   * <p>
528   * Coercion is performed to the given type.
529   *
530   * @param fieldName field name or position to return
531   * @return Comparable
532   */
533  public Object getObject( Comparable fieldName, Type type )
534    {
535    int pos = fields.getPos( asFieldName( fieldName ) );
536
537    if( pos > coercions.length - 1 )
538      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
539
540    return Coercions.coerce( coercions[ pos ], tuple.getObject( pos ), type );
541    }
542
543  /**
544   * Method set sets the value in the given position.
545   * <p>
546   * No coercion is performed if there is an associated coercible type.
547   *
548   * @param pos   position to set
549   * @param value of type Comparable
550   */
551  public void setRaw( int pos, Object value )
552    {
553    tuple.set( pos, value );
554    }
555
556  /**
557   * Method set sets the value in the given field or position.
558   * <p>
559   * No coercion is performed if there is an associated coercible type.
560   *
561   * @param fieldName field name or position to set
562   * @param value     of type Comparable
563   */
564  public void setRaw( Comparable fieldName, Object value )
565    {
566    tuple.set( fields.getPos( asFieldName( fieldName ) ), value );
567    }
568
569  /**
570   * Method set sets the value in the given field or position.
571   *
572   * @param fieldName field name or position to set
573   * @param value     of type Comparable
574   */
575  public void setObject( Comparable fieldName, Object value )
576    {
577    int pos = fields.getPos( asFieldName( fieldName ) );
578
579    if( pos > coercions.length - 1 )
580      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
581
582    tuple.set( pos, coercions[ pos ].canonical( value ) );
583    }
584
585  /**
586   * Method setBoolean sets the value in the given field or position.
587   *
588   * @param fieldName field name or position to set
589   * @param value     of type boolean
590   */
591  public void setBoolean( Comparable fieldName, boolean value )
592    {
593    int pos = fields.getPos( asFieldName( fieldName ) );
594
595    if( pos > coercions.length - 1 )
596      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
597
598    tuple.set( pos, coercions[ pos ].canonical( value ) );
599    }
600
601  /**
602   * Method setShort sets the value in the given field or position.
603   *
604   * @param fieldName field name or position to set
605   * @param value     of type short
606   */
607  public void setShort( Comparable fieldName, short value )
608    {
609    int pos = fields.getPos( asFieldName( fieldName ) );
610
611    if( pos > coercions.length - 1 )
612      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
613
614    tuple.set( pos, coercions[ pos ].canonical( value ) );
615    }
616
617  /**
618   * Method setInteger sets the value in the given field or position.
619   *
620   * @param fieldName field name or position to set
621   * @param value     of type int
622   */
623  public void setInteger( Comparable fieldName, int value )
624    {
625    int pos = fields.getPos( asFieldName( fieldName ) );
626
627    if( pos > coercions.length - 1 )
628      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
629
630    tuple.set( pos, coercions[ pos ].canonical( value ) );
631    }
632
633  /**
634   * Method setLong sets the value in the given field or position.
635   *
636   * @param fieldName field name or position to set
637   * @param value     of type long
638   */
639  public void setLong( Comparable fieldName, long value )
640    {
641    int pos = fields.getPos( asFieldName( fieldName ) );
642
643    if( pos > coercions.length - 1 )
644      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
645
646    tuple.set( pos, coercions[ pos ].canonical( value ) );
647    }
648
649  /**
650   * Method setFloat sets the value in the given field or position.
651   *
652   * @param fieldName field name or position to set
653   * @param value     of type float
654   */
655  public void setFloat( Comparable fieldName, float value )
656    {
657    int pos = fields.getPos( asFieldName( fieldName ) );
658
659    if( pos > coercions.length - 1 )
660      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
661
662    tuple.set( pos, coercions[ pos ].canonical( value ) );
663    }
664
665  /**
666   * Method setDouble sets the value in the given field or position.
667   *
668   * @param fieldName field name or position to set
669   * @param value     of type double
670   */
671  public void setDouble( Comparable fieldName, double value )
672    {
673    int pos = fields.getPos( asFieldName( fieldName ) );
674
675    if( pos > coercions.length - 1 )
676      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
677
678    tuple.set( pos, coercions[ pos ].canonical( value ) );
679    }
680
681  /**
682   * Method setString sets the value in the given field or position.
683   *
684   * @param fieldName field name or position to set
685   * @param value     of type String
686   */
687  public void setString( Comparable fieldName, String value )
688    {
689    int pos = fields.getPos( asFieldName( fieldName ) );
690
691    if( pos > coercions.length - 1 )
692      throw new TupleException( "position value is too large: " + pos + ", positions in field: " + tuple.size() );
693
694    tuple.set( pos, coercions[ pos ].canonical( value ) );
695    }
696
697  /**
698   * Method getString returns the element for the given field name or position as a String.
699   * <br>
700   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
701   * be considered.
702   *
703   * @param fieldName field name or position to return
704   * @return String
705   */
706  public String getString( Comparable fieldName )
707    {
708    return (String) getObject( fieldName, String.class );
709    }
710
711  /**
712   * Method getFloat returns the element for the given field name or position as a float. Zero if null.
713   * <br>
714   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
715   * be considered.
716   *
717   * @param fieldName field name or position to return
718   * @return float
719   */
720  public float getFloat( Comparable fieldName )
721    {
722    return (Float) getObject( fieldName, float.class );
723    }
724
725  /**
726   * Method getDouble returns the element for the given field name or position as a double. Zero if null.
727   * <br>
728   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
729   * be considered.
730   *
731   * @param fieldName field name or position to return
732   * @return double
733   */
734  public double getDouble( Comparable fieldName )
735    {
736    return (Double) getObject( fieldName, double.class );
737    }
738
739  /**
740   * Method getInteger  returns the element for the given field name or position as an int. Zero if null.
741   * <br>
742   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
743   * be considered.
744   *
745   * @param fieldName field name or position to return
746   * @return int
747   */
748  public int getInteger( Comparable fieldName )
749    {
750    return (Integer) getObject( fieldName, int.class );
751    }
752
753  /**
754   * Method getLong returns the element for the given field name or position as a long. Zero if null.
755   * <br>
756   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
757   * be considered.
758   *
759   * @param fieldName field name or position to return
760   * @return long
761   */
762  public long getLong( Comparable fieldName )
763    {
764    return (Long) getObject( fieldName, long.class );
765    }
766
767  /**
768   * Method getShort returns the element for the given field name or position as a short. Zero if null.
769   * <br>
770   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
771   * be considered.
772   *
773   * @param fieldName field name or position to return
774   * @return short
775   */
776  public short getShort( Comparable fieldName )
777    {
778    return (Short) getObject( fieldName, short.class );
779    }
780
781  /**
782   * Method getBoolean returns the element for the given field name or position as a boolean.
783   * If the value is (case ignored) the string 'true', a {@code true} value will be returned. {@code false} if null.
784   * <br>
785   * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will
786   * be considered.
787   *
788   * @param fieldName field name or position to return
789   * @return boolean
790   */
791  public boolean getBoolean( Comparable fieldName )
792    {
793    return (Boolean) getObject( fieldName, boolean.class );
794    }
795
796  private Comparable asFieldName( Comparable fieldName )
797    {
798    return Fields.asFieldName( fieldName );
799    }
800
801  /**
802   * Method selectEntry selects the fields specified in the selector from this instance. If {@link Fields#ALL} or the
803   * same fields as declared are given, {@code this} will be returned.
804   * <p>
805   * The returned TupleEntry will be either modifiable or unmodifiable, depending on the state of this TupleEntry instance.
806   * <p>
807   * See {@link #selectEntryCopy(Fields)} to guarantee a copy suitable for modifying or caching/storing in a collection.
808   * <p>
809   * Note this is a bug fix and change from 2.0 and 2.1. In previous versions the modifiable state was dependent
810   * on the given selector.
811   *
812   * @param selector Fields selector that selects the values to return
813   * @return TupleEntry
814   */
815  public TupleEntry selectEntry( Fields selector )
816    {
817    if( selector == null || selector.isAll() || fields == selector ) // == is intentional
818      return this;
819
820    if( selector.isNone() )
821      return isUnmodifiable ? TupleEntry.NULL : new TupleEntry();
822
823    return new TupleEntry( Fields.asDeclaration( selector ), tuple.get( this.fields, selector ), isUnmodifiable );
824    }
825
826  /**
827   * Method selectEntry selects the fields specified in selector from this instance.
828   * <p>
829   * It is guaranteed to return a new modifiable TupleEntry instance at a cost of copying data.
830   * <p>
831   * The returned instance is safe to cache.
832   *
833   * @param selector Fields selector that selects the values to return
834   * @return TupleEntry
835   */
836  public TupleEntry selectEntryCopy( Fields selector )
837    {
838    if( selector == null || selector.isAll() || fields == selector ) // == is intentional
839      return new TupleEntry( this );
840
841    if( selector.isNone() )
842      return new TupleEntry();
843
844    return new TupleEntry( Fields.asDeclaration( selector ), tuple.get( this.fields, selector ) );
845    }
846
847  /**
848   * Method selectTuple selects the fields specified in the selector from this instance. If {@link Fields#ALL} or the
849   * same fields as declared are given, {@code this.getTuple()} will be returned.
850   * <p>
851   * The returned Tuple will be either modifiable or unmodifiable, depending on the state of this TupleEntry instance.
852   * <p>
853   * See {@link #selectTupleCopy(Fields)} to guarantee a copy suitable for modifying or caching/storing in a collection.
854   * <p>
855   * Note this is a bug fix and change from 2.0 and 2.1. In previous versions the modifiable state was dependent
856   * on the given selector.
857   *
858   * @param selector Fields selector that selects the values to return
859   * @return Tuple
860   */
861  public Tuple selectTuple( Fields selector )
862    {
863    if( selector == null || selector.isAll() || fields == selector ) // == is intentional
864      return this.tuple;
865
866    if( selector.isNone() )
867      return isUnmodifiable ? Tuple.NULL : new Tuple();
868
869    Tuple result = tuple.get( fields, selector );
870
871    if( isUnmodifiable )
872      Tuples.asUnmodifiable( result );
873
874    return result;
875    }
876
877  /**
878   * Method selectTupleCopy selects the fields specified in selector from this instance.
879   * <p>
880   * It is guaranteed to return a new modifiable Tuple instance at a cost of copying data.
881   * <p>
882   * The returned instance is safe to cache.
883   *
884   * @param selector Fields selector that selects the values to return
885   * @return Tuple
886   */
887  public Tuple selectTupleCopy( Fields selector )
888    {
889    if( selector == null || selector.isAll() || fields == selector ) // == is intentional
890      return new Tuple( this.tuple );
891
892    if( selector.isNone() )
893      return new Tuple();
894
895    return tuple.get( fields, selector );
896    }
897
898  /**
899   * Method selectInto selects the fields specified in the selector from this instance and copies
900   * them into the given tuple argument.
901   *
902   * @param selector of type Fields
903   * @param tuple    of type Tuple
904   * @return returns the given tuple argument with new values added
905   */
906  public Tuple selectInto( Fields selector, Tuple tuple )
907    {
908    if( selector.isNone() )
909      return tuple;
910
911    int[] pos = this.tuple.getPos( fields, selector );
912
913    if( pos == null || pos.length == 0 )
914      {
915      tuple.addAll( this.tuple );
916      }
917    else
918      {
919      for( int i : pos )
920        tuple.add( this.tuple.getObject( i ) );
921      }
922
923    return tuple;
924    }
925
926  /**
927   * Method setTuple sets the values specified by the selector to the values given by the given tuple, the given
928   * values will always be copied into this TupleEntry.
929   *
930   * @param selector of type Fields
931   * @param tuple    of type Tuple
932   */
933  public void setTuple( Fields selector, Tuple tuple )
934    {
935    if( selector == null || selector.isAll() )
936      this.tuple.setAll( tuple );
937    else
938      this.tuple.set( fields, selector, tuple );
939    }
940
941  /**
942   * Method set sets the values from the given tupleEntry into this TupleEntry instance based on the given
943   * tupleEntry field names.
944   * <p>
945   * If type information is given, each incoming value will be coerced from its canonical type to the current type.
946   *
947   * @param tupleEntry of type TupleEntry
948   */
949  public void set( TupleEntry tupleEntry )
950    {
951    this.tuple.set( fields, tupleEntry.getFields(), tupleEntry.getTuple(), tupleEntry.coercions );
952    }
953
954  /**
955   * Method appendNew appends the given TupleEntry instance to this instance.
956   *
957   * @param entry of type TupleEntry
958   * @return TupleEntry
959   */
960  public TupleEntry appendNew( TupleEntry entry )
961    {
962    Fields appendedFields = fields.append( entry.fields.isUnknown() ? Fields.size( entry.tuple.size() ) : entry.fields );
963    Tuple appendedTuple = tuple.append( entry.tuple );
964
965    return new TupleEntry( appendedFields, appendedTuple );
966    }
967
968  @Override
969  public boolean equals( Object object )
970    {
971    if( this == object )
972      return true;
973
974    if( !( object instanceof TupleEntry ) )
975      return false;
976
977    TupleEntry that = (TupleEntry) object;
978
979    if( fields != null ? !fields.equals( that.fields ) : that.fields != null )
980      return false;
981
982    // use comparators if in the this side fields instance
983    if( tuple != null ? fields.compare( tuple, that.tuple ) != 0 : that.tuple != null )
984      return false;
985
986    return true;
987    }
988
989  @Override
990  public int hashCode()
991    {
992    int result = fields != null ? fields.hashCode() : 0;
993    result = 31 * result + ( tuple != null ? tuple.hashCode() : 0 );
994    return result;
995    }
996
997  @Override
998  public String toString()
999    {
1000    if( fields == null )
1001      return "empty";
1002    else if( tuple == null )
1003      return "fields: " + fields.print();
1004    else
1005      return "fields: " + fields.print() + " tuple: " + tuple.print();
1006    }
1007
1008  /**
1009   * Method asIterableOf returns an {@link Iterable} instance that will coerce all Tuple elements
1010   * into the given {@code type} parameter.
1011   * <p>
1012   * This method honors any {@link cascading.tuple.type.CoercibleType} instances on the internal
1013   * Fields instance for the specified Tuple element.
1014   *
1015   * @param type of type Class
1016   * @return an Iterable
1017   */
1018  public <T> Iterable<T> asIterableOf( final Class<T> type )
1019    {
1020    return () ->
1021    {
1022    final Iterator<CoercibleType> coercibleIterator = coercions.length == 0 ? OBJECT_ITERATOR : Arrays.asList( coercions ).iterator();
1023    final Iterator valuesIterator = tuple.iterator();
1024
1025    return new Iterator<T>()
1026      {
1027      @Override
1028      public boolean hasNext()
1029        {
1030        return valuesIterator.hasNext();
1031        }
1032
1033      @Override
1034      public T next()
1035        {
1036        Object next = valuesIterator.next();
1037
1038        return (T) coercibleIterator.next().coerce( next, type );
1039        }
1040
1041      @Override
1042      public void remove()
1043        {
1044        valuesIterator.remove();
1045        }
1046      };
1047    };
1048    }
1049  }