001/*
002 * Copyright (c) 2016-2019 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.io.Serializable;
026import java.lang.reflect.Type;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.Formatter;
031import java.util.Iterator;
032import java.util.List;
033
034import cascading.tuple.coerce.Coercions;
035import cascading.tuple.type.CoercibleType;
036import cascading.util.Util;
037
038/**
039 * A Tuple represents a set of values. Consider a Tuple the same as a database record where every value is a column in
040 * that table.
041 * <p>
042 * A "tuple stream" is a set of Tuple instances passed consecutively through a Pipe assembly.
043 * <p>
044 * Tuples work in tandem with {@link Fields} and the {@link TupleEntry} classes. A TupleEntry holds an instance of
045 * Fields and a Tuple. It allows a tuple to be accessed by its field names, and will help maintain consistent types
046 * if any are given on the Fields instance. That is, if a field is specified at an Integer, calling {@link #set(int, Object)}
047 * with a String will force the String to be coerced into a Integer instance.
048 * <p>
049 * For managing custom types, see the {@link CoercibleType} interface which extends {@link Type}.
050 * <p>
051 * Tuple instances created by user code, by default, are mutable (or modifiable).
052 * Tuple instances created by the system are immutable (or unmodifiable, tested by calling {@link #isUnmodifiable()}).
053 * <p>
054 * For example tuples returned by
055 * {@link cascading.operation.FunctionCall#getArguments()}, will always be unmodifiable. Thus they must be copied
056 * if they will be changed by user code or cached in the local context. See the Tuple copy constructor, or {@code *Copy()} methods
057 * on {@link TupleEntry}.
058 * <p>
059 * Because a Tuple can hold any Object type, it is suitable for storing custom types. But all custom types
060 * must have a serialization support per the underlying framework.
061 * <p>
062 * For Hadoop, a {@link org.apache.hadoop.io.serializer.Serialization} implementation
063 * must be registered with Hadoop. For further performance improvements, see the
064 * {@link cascading.tuple.hadoop.SerializationToken} Java annotation.
065 *
066 * @see org.apache.hadoop.io.serializer.Serialization
067 * @see cascading.tuple.hadoop.SerializationToken
068 */
069public class Tuple implements Comparable<Object>, Iterable<Object>, Serializable
070  {
071  /** A constant empty Tuple instance. This instance is immutable. */
072  public static final Tuple NULL = Tuples.asUnmodifiable( new Tuple() );
073
074  /** Field printDelim */
075  private final static String printDelim = "\t";
076
077  /** Field isUnmodifiable */
078  protected transient boolean isUnmodifiable = false;
079
080  /** Field elements */
081  protected List<Object> elements;
082
083  /**
084   * Method size returns a new Tuple instance of the given size with nulls as its element values.
085   *
086   * @param size of type int
087   * @return Tuple
088   */
089  public static Tuple size( int size )
090    {
091    return size( size, null );
092    }
093
094  /**
095   * Method size returns a new Tuple instance of the given size with the given Comparable as its element values.
096   *
097   * @param size  of type int
098   * @param value of type Comparable
099   * @return Tuple
100   */
101  public static Tuple size( int size, Comparable value )
102    {
103    Tuple result = new Tuple( new ArrayList<>( size ) );
104
105    for( int i = 0; i < size; i++ )
106      result.elements.add( value ); // skip modifiable check
107
108    return result;
109    }
110
111  /**
112   * Returns a reference to the private elements of the given Tuple.
113   * <p>
114   * This method is for internal use and is subject to change scope in a future release.
115   *
116   * @param tuple of type Tuple
117   * @return List
118   */
119  public static List<Object> elements( Tuple tuple )
120    {
121    return tuple.elements;
122    }
123
124  protected Tuple( List<Object> elements )
125    {
126    this.elements = elements;
127    }
128
129  /** Constructor Tuple creates a new Tuple instance. */
130  public Tuple()
131    {
132    this( new ArrayList<>() );
133    }
134
135  /**
136   * Copy constructor. Does not nest the given Tuple instance within this new instance. Use {@link #add(Object)}.
137   *
138   * @param tuple of type Tuple
139   */
140  @ConstructorProperties({"tuple"})
141  public Tuple( Tuple tuple )
142    {
143    this( new ArrayList<>( tuple.elements ) );
144    }
145
146  /**
147   * Constructor Tuple creates a new Tuple instance with all the given values.
148   *
149   * @param values of type Object...
150   */
151  @ConstructorProperties({"values"})
152  public Tuple( Object... values )
153    {
154    this( new ArrayList<>( values.length ) );
155    Collections.addAll( elements, values );
156    }
157
158  /**
159   * Method isUnmodifiable returns true if this Tuple instance is unmodifiable.
160   * <p>
161   * "Unmodifiable" tuples are generally owned by the system and cannot be changed, nor should they be cached
162   * as the internal values may change.
163   *
164   * @return boolean
165   */
166  public boolean isUnmodifiable()
167    {
168    return isUnmodifiable;
169    }
170
171  /**
172   * Method get returns the element at the given position.
173   * <p>
174   * This method will perform no coercion on the element.
175   *
176   * @param pos of type int
177   * @return Object
178   */
179  public Object getObject( int pos )
180    {
181    return elements.get( pos );
182    }
183
184  /**
185   * Method getChar returns the element at the given position as a char.
186   *
187   * @param pos of type int
188   * @return String
189   */
190  public char getChar( int pos )
191    {
192    return Coercions.CHARACTER.coerce( getObject( pos ) );
193    }
194
195  /**
196   * Method getString returns the element at the given position as a String.
197   *
198   * @param pos of type int
199   * @return String
200   */
201  public String getString( int pos )
202    {
203    return Coercions.STRING.coerce( getObject( pos ) );
204    }
205
206  /**
207   * Method getFloat returns the element at the given position as a float. Zero if null.
208   *
209   * @param pos of type int
210   * @return float
211   */
212  public float getFloat( int pos )
213    {
214    return Coercions.FLOAT.coerce( getObject( pos ) );
215    }
216
217  /**
218   * Method getDouble returns the element at the given position as a double. Zero if null.
219   *
220   * @param pos of type int
221   * @return double
222   */
223  public double getDouble( int pos )
224    {
225    return Coercions.DOUBLE.coerce( getObject( pos ) );
226    }
227
228  /**
229   * Method getInteger returns the element at the given position as an int. Zero if null.
230   *
231   * @param pos of type int
232   * @return int
233   */
234  public int getInteger( int pos )
235    {
236    return Coercions.INTEGER.coerce( getObject( pos ) );
237    }
238
239  /**
240   * Method getLong returns the element at the given position as an long. Zero if null.
241   *
242   * @param pos of type int
243   * @return long
244   */
245  public long getLong( int pos )
246    {
247    return Coercions.LONG.coerce( getObject( pos ) );
248    }
249
250  /**
251   * Method getShort returns the element at the given position as an short. Zero if null.
252   *
253   * @param pos of type int
254   * @return long
255   */
256  public short getShort( int pos )
257    {
258    return Coercions.SHORT.coerce( getObject( pos ) );
259    }
260
261  /**
262   * Method getBoolean returns the element at the given position as a boolean. If the value is (case ignored) the
263   * string 'true', a {@code true} value will be returned. {@code false} if null.
264   *
265   * @param pos of type int
266   * @return boolean
267   */
268  public boolean getBoolean( int pos )
269    {
270    return Coercions.BOOLEAN.coerce( getObject( pos ) );
271    }
272
273  /**
274   * Method get will return a new Tuple instance populated with element values from the given array of positions.
275   *
276   * @param pos of type int[]
277   * @return Tuple
278   */
279  public Tuple get( int[] pos )
280    {
281    if( pos == null || pos.length == 0 )
282      return new Tuple( this );
283
284    Tuple results = new Tuple( new ArrayList<>( pos.length ) );
285
286    for( int i : pos )
287      results.elements.add( elements.get( i ) ); // skip modifiable check
288
289    return results;
290    }
291
292  /**
293   * Method get returns a new Tuple populated with only those values whose field names are specified in the given
294   * selector. The declarator Fields instance declares the fields and positions in the current Tuple instance.
295   *
296   * @param declarator of type Fields
297   * @param selector   of type Fields
298   * @return Tuple
299   */
300  public Tuple get( Fields declarator, Fields selector )
301    {
302    try
303      {
304      return get( getPos( declarator, selector ) );
305      }
306    catch( Exception exception )
307      {
308      throw new TupleException( "unable to select from: " + declarator.print() + ", using selector: " + selector.print(), exception );
309      }
310    }
311
312  public int[] getPos( Fields declarator, Fields selector )
313    {
314    if( !declarator.isUnknown() && elements.size() != declarator.size() )
315      throw new TupleException( "field declaration: " + declarator.print() + ", does not match tuple: " + print() );
316
317    return declarator.getPos( selector, size() );
318    }
319
320  /**
321   * Method is the inverse of {@link #remove(int[])}.
322   *
323   * @param pos of type int[]
324   * @return Tuple
325   */
326  public Tuple leave( int[] pos )
327    {
328    verifyModifiable();
329
330    Tuple results = remove( pos );
331
332    List<Object> temp = results.elements;
333    results.elements = this.elements;
334    this.elements = temp;
335
336    return results;
337    }
338
339  /** Method clear empties this Tuple instance. A subsequent call to {@link #size()} will return zero ({@code 0}). */
340  public void clear()
341    {
342    verifyModifiable();
343
344    elements.clear();
345    }
346
347  /**
348   * Method add adds a new element value to this instance.
349   *
350   * @param value of type Comparable
351   */
352  public void add( Comparable value )
353    {
354    add( (Object) value );
355    }
356
357  /**
358   * Method add adds a new element value to this instance.
359   *
360   * @param value of type Object
361   */
362  public void add( Object value )
363    {
364    verifyModifiable();
365
366    elements.add( value );
367    }
368
369  /**
370   * Method addBoolean adds a new element value to this instance.
371   *
372   * @param value of type boolean
373   */
374  public void addBoolean( boolean value )
375    {
376    verifyModifiable();
377
378    elements.add( value );
379    }
380
381  /**
382   * Method addShort adds a new element value to this instance.
383   *
384   * @param value of type short
385   */
386  public void addShort( short value )
387    {
388    verifyModifiable();
389
390    elements.add( value );
391    }
392
393  /**
394   * Method addInteger adds a new element value to this instance.
395   *
396   * @param value of type int
397   */
398  public void addInteger( int value )
399    {
400    verifyModifiable();
401
402    elements.add( value );
403    }
404
405  /**
406   * Method addLong adds a new element value to this instance.
407   *
408   * @param value of type long
409   */
410  public void addLong( long value )
411    {
412    verifyModifiable();
413
414    elements.add( value );
415    }
416
417  /**
418   * Method addFloat adds a new element value to this instance.
419   *
420   * @param value of type float
421   */
422  public void addFloat( float value )
423    {
424    verifyModifiable();
425
426    elements.add( value );
427    }
428
429  /**
430   * Method addDouble adds a new element value to this instance.
431   *
432   * @param value of type double
433   */
434  public void addDouble( double value )
435    {
436    verifyModifiable();
437
438    elements.add( value );
439    }
440
441  /**
442   * Method addString adds a new element value to this instance.
443   *
444   * @param value of type String
445   */
446  public void addString( String value )
447    {
448    verifyModifiable();
449
450    elements.add( value );
451    }
452
453  /**
454   * Method addAll adds all given values to this instance.
455   *
456   * @param values of type Object...
457   */
458  public void addAll( Object... values )
459    {
460    verifyModifiable();
461
462    if( values.length == 1 && values[ 0 ] instanceof Tuple )
463      addAll( (Tuple) values[ 0 ] );
464    else
465      Collections.addAll( elements, values );
466    }
467
468  /**
469   * Method addAll adds all the element values of the given Tuple instance to this instance.
470   *
471   * @param tuple of type Tuple
472   */
473  public void addAll( Tuple tuple )
474    {
475    verifyModifiable();
476
477    if( tuple != null )
478      elements.addAll( tuple.elements );
479    }
480
481  /**
482   * Method setAll sets each element value of the given Tuple instance into the corresponding
483   * position of this instance.
484   *
485   * @param tuple of type Tuple
486   */
487  public void setAll( Tuple tuple )
488    {
489    verifyModifiable();
490
491    if( tuple == null )
492      return;
493
494    for( int i = 0; i < tuple.elements.size(); i++ )
495      internalSet( i, tuple.elements.get( i ) );
496    }
497
498  /**
499   * Method setAll sets each element value of the given Tuple instances into the corresponding
500   * position of this instance.
501   * <p>
502   * All given tuple instances after the first will be offset by the length of the prior tuple instances.
503   *
504   * @param tuples of type Tuple[]
505   */
506  public void setAll( Tuple... tuples )
507    {
508    verifyModifiable();
509
510    if( tuples.length == 0 )
511      return;
512
513    int pos = 0;
514    for( int i = 0; i < tuples.length; i++ )
515      {
516      Tuple tuple = tuples[ i ];
517
518      if( tuple == null ) // being defensive
519        continue;
520
521      for( int j = 0; j < tuple.elements.size(); j++ )
522        internalSet( pos++, tuple.elements.get( j ) );
523      }
524    }
525
526  /**
527   * Method set sets the given value to the given index position in this instance.
528   *
529   * @param index of type int
530   * @param value of type Object
531   */
532  public void set( int index, Object value )
533    {
534    verifyModifiable();
535
536    internalSet( index, value );
537    }
538
539  /**
540   * Method setBoolean sets the given value to the given index position in this instance.
541   *
542   * @param index of type int
543   * @param value of type boolean
544   */
545  public void setBoolean( int index, boolean value )
546    {
547    verifyModifiable();
548
549    internalSet( index, value );
550    }
551
552  /**
553   * Method setShort sets the given value to the given index position in this instance.
554   *
555   * @param index of type int
556   * @param value of type short
557   */
558  public void setShort( int index, short value )
559    {
560    verifyModifiable();
561
562    internalSet( index, value );
563    }
564
565  /**
566   * Method setInteger sets the given value to the given index position in this instance.
567   *
568   * @param index of type int
569   * @param value of type int
570   */
571  public void setInteger( int index, int value )
572    {
573    verifyModifiable();
574
575    internalSet( index, value );
576    }
577
578  /**
579   * Method setLong sets the given value to the given index position in this instance.
580   *
581   * @param index of type int
582   * @param value of type long
583   */
584  public void setLong( int index, long value )
585    {
586    verifyModifiable();
587
588    internalSet( index, value );
589    }
590
591  /**
592   * Method setFloat sets the given value to the given index position in this instance.
593   *
594   * @param index of type int
595   * @param value of type float
596   */
597  public void setFloat( int index, float value )
598    {
599    verifyModifiable();
600
601    internalSet( index, value );
602    }
603
604  /**
605   * Method setDouble sets the given value to the given index position in this instance.
606   *
607   * @param index of type int
608   * @param value of type double
609   */
610  public void setDouble( int index, double value )
611    {
612    verifyModifiable();
613
614    internalSet( index, value );
615    }
616
617  /**
618   * Method setString sets the given value to the given index position in this instance.
619   *
620   * @param index of type int
621   * @param value of type String
622   */
623  public void setString( int index, String value )
624    {
625    verifyModifiable();
626
627    internalSet( index, value );
628    }
629
630  protected final void internalSet( int index, Object value )
631    {
632    try
633      {
634      elements.set( index, value );
635      }
636    catch( IndexOutOfBoundsException exception )
637      {
638      if( elements.size() != 0 )
639        throw new TupleException( "failed to set a value beyond the end of the tuple elements array, size: " + size() + " , index: " + index );
640      else
641        throw new TupleException( "failed to set a value, tuple may not be initialized with values, is zero length" );
642      }
643    }
644
645  /**
646   * Method put places the values of the given tuple into the positions specified by the fields argument. The declarator
647   * Fields value declares the fields in this Tuple instance.
648   *
649   * @param declarator of type Fields
650   * @param fields     of type Fields
651   * @param tuple      of type Tuple
652   */
653  public void put( Fields declarator, Fields fields, Tuple tuple )
654    {
655    verifyModifiable();
656
657    int[] pos = getPos( declarator, fields );
658
659    for( int i = 0; i < pos.length; i++ )
660      internalSet( pos[ i ], tuple.getObject( i ) );
661    }
662
663  /**
664   * Method remove removes the values specified by the given pos array and returns a new Tuple containing the
665   * removed values.
666   *
667   * @param pos of type int[]
668   * @return Tuple
669   */
670  public Tuple remove( int[] pos )
671    {
672    verifyModifiable();
673
674    // calculate offsets to apply when removing values from elements
675    int offset[] = new int[ pos.length ];
676
677    for( int i = 0; i < pos.length; i++ )
678      {
679      offset[ i ] = 0;
680
681      for( int j = 0; j < i; j++ )
682        {
683        if( pos[ j ] < pos[ i ] )
684          offset[ i ]++;
685        }
686      }
687
688    Tuple results = new Tuple();
689
690    for( int i = 0; i < pos.length; i++ )
691      results.add( elements.remove( pos[ i ] - offset[ i ] ) );
692
693    return results;
694    }
695
696  /**
697   * Method remove removes the values specified by the given selector. The declarator declares the fields in this instance.
698   *
699   * @param declarator of type Fields
700   * @param selector   of type Fields
701   * @return Tuple
702   */
703  public Tuple remove( Fields declarator, Fields selector )
704    {
705    return remove( getPos( declarator, selector ) );
706    }
707
708  /**
709   * Creates a new Tuple from the given positions, but sets the values in the current tuple to null.
710   *
711   * @param pos of type int[]
712   * @return Tuple
713   */
714  Tuple extract( int[] pos )
715    {
716    Tuple results = new Tuple();
717
718    for( int i : pos )
719      results.add( elements.set( i, null ) );
720
721    return results;
722    }
723
724  Tuple nulledCopy( int[] pos )
725    {
726    if( pos == null )
727      return size( size() );
728
729    Tuple results = new Tuple( this );
730
731    for( int i : pos )
732      results.set( i, null );
733
734    return results;
735    }
736
737  /**
738   * Sets the values in the given positions to the values from the given Tuple.
739   *
740   * @param pos   of type int[]
741   * @param tuple of type Tuple
742   */
743  void set( int[] pos, Tuple tuple )
744    {
745    verifyModifiable();
746
747    if( pos.length != tuple.size() )
748      throw new TupleException( "given tuple not same size as position array: " + pos.length + ", tuple: " + tuple.print() );
749
750    int count = 0;
751    for( int i : pos )
752      elements.set( i, tuple.elements.get( count++ ) );
753    }
754
755  private void set( int[] pos, Type[] types, Tuple tuple, CoercibleType[] coercions )
756    {
757    verifyModifiable();
758
759    if( pos.length != tuple.size() )
760      throw new TupleException( "given tuple not same size as position array: " + pos.length + ", tuple: " + tuple.print() );
761
762    int count = 0;
763
764    for( int i : pos )
765      {
766      Object element = tuple.elements.get( count );
767
768      if( types != null )
769        {
770        Type type = types[ i ];
771        element = coercions[ count ].coerce( element, type );
772        }
773
774      elements.set( i, element );
775
776      count++;
777      }
778    }
779
780  /**
781   * Method set sets the values in the given selector positions to the values from the given Tuple.
782   * <p>
783   * If type information is given, each incoming value from tuple will be coerced from its canonical type to the
784   * current type as declared in the declarator.
785   *
786   * @param declarator of type Fields
787   * @param selector   of type Fields
788   * @param tuple      of type Tuple
789   */
790  public void set( Fields declarator, Fields selector, Tuple tuple )
791    {
792    try
793      {
794      set( declarator.getPos( selector ), declarator.getTypes(), tuple, TupleEntry.getCoercions( declarator, tuple ) );
795      }
796    catch( Exception exception )
797      {
798      throw new TupleException( "unable to set into: " + declarator.print() + ", using selector: " + selector.print(), exception );
799      }
800    }
801
802  protected void set( Fields declarator, Fields selector, Tuple tuple, CoercibleType[] coercions )
803    {
804    try
805      {
806      set( declarator.getPos( selector ), declarator.getTypes(), tuple, coercions );
807      }
808    catch( Exception exception )
809      {
810      throw new TupleException( "unable to set into: " + declarator.print() + ", using selector: " + selector.print(), exception );
811      }
812    }
813
814  /**
815   * Method iterator returns an {@link Iterator} over this Tuple instances values.
816   *
817   * @return Iterator
818   */
819  public Iterator<Object> iterator()
820    {
821    return elements.iterator();
822    }
823
824  /**
825   * Method isEmpty returns true if this Tuple instance has no values.
826   *
827   * @return the empty (type boolean) of this Tuple object.
828   */
829  public boolean isEmpty()
830    {
831    return elements.isEmpty();
832    }
833
834  /**
835   * Method size returns the number of values in this Tuple instance.
836   *
837   * @return int
838   */
839  public int size()
840    {
841    return elements.size();
842    }
843
844  /**
845   * Method elements returns a new Object[] array of this Tuple instances values.
846   *
847   * @return Object[]
848   */
849  private Object[] elements()
850    {
851    return elements.toArray();
852    }
853
854  /**
855   * Method elements returns the given destination array with the values of This tuple instance.
856   *
857   * @param destination of type Object[]
858   * @return Object[]
859   */
860  <T> T[] elements( T[] destination )
861    {
862    return elements.toArray( destination );
863    }
864
865  /**
866   * Method getTypes returns an array of the element classes. Null if the element is null.
867   *
868   * @return the types (type Class[]) of this Tuple object.
869   */
870  public Class[] getTypes()
871    {
872    Class[] types = new Class[ elements.size() ];
873
874    for( int i = 0; i < elements.size(); i++ )
875      {
876      Object value = elements.get( i );
877
878      if( value != null )
879        types[ i ] = value.getClass();
880      }
881
882    return types;
883    }
884
885  /**
886   * Method append appends all the values of the given Tuple instances to a copy of this instance.
887   *
888   * @param tuples of type Tuple
889   * @return Tuple
890   */
891  public Tuple append( Tuple... tuples )
892    {
893    Tuple result = new Tuple( this );
894
895    for( Tuple tuple : tuples )
896      result.addAll( tuple );
897
898    return result;
899    }
900
901  /**
902   * Method compareTo compares this Tuple to the given Tuple instance.
903   *
904   * @param other of type Tuple
905   * @return int
906   */
907  public int compareTo( Tuple other )
908    {
909    if( other == null || other.elements == null )
910      return 1;
911
912    if( other.elements.size() != this.elements.size() )
913      return this.elements.size() - other.elements.size();
914
915    for( int i = 0; i < this.elements.size(); i++ )
916      {
917      Comparable lhs = (Comparable) this.elements.get( i );
918      Comparable rhs = (Comparable) other.elements.get( i );
919
920      if( lhs == null && rhs == null )
921        continue;
922
923      if( lhs == null )
924        return -1;
925      else if( rhs == null )
926        return 1;
927
928      int c = lhs.compareTo( rhs ); // guaranteed to not be null
929      if( c != 0 )
930        return c;
931      }
932
933    return 0;
934    }
935
936  public int compareTo( Comparator[] comparators, Tuple other )
937    {
938    if( comparators == null )
939      return compareTo( other );
940
941    if( other == null || other.elements == null )
942      return 1;
943
944    if( other.elements.size() != this.elements.size() )
945      return this.elements.size() - other.elements.size();
946
947    if( comparators.length != this.elements.size() )
948      throw new IllegalArgumentException( "comparator array not same size as tuple elements" );
949
950    for( int i = 0; i < this.elements.size(); i++ )
951      {
952      Object lhs = this.elements.get( i );
953      Object rhs = other.elements.get( i );
954
955      int c;
956
957      if( comparators[ i ] != null )
958        c = comparators[ i ].compare( lhs, rhs );
959      else if( lhs == null && rhs == null )
960        c = 0;
961      else if( lhs == null )
962        return -1;
963      else if( rhs == null )
964        return 1;
965      else
966        c = ( (Comparable) lhs ).compareTo( rhs ); // guaranteed to not be null
967
968      if( c != 0 )
969        return c;
970      }
971
972    return 0;
973    }
974
975  /**
976   * Method compareTo implements the {@link Comparable#compareTo(Object)} method.
977   *
978   * @param other of type Object
979   * @return int
980   */
981  public int compareTo( Object other )
982    {
983    if( other instanceof Tuple )
984      return compareTo( (Tuple) other );
985    else
986      return -1;
987    }
988
989  @SuppressWarnings({"ForLoopReplaceableByForEach"})
990  @Override
991  public boolean equals( Object object )
992    {
993    if( !( object instanceof Tuple ) )
994      return false;
995
996    Tuple other = (Tuple) object;
997
998    if( this.elements.size() != other.elements.size() )
999      return false;
1000
1001    for( int i = 0; i < this.elements.size(); i++ )
1002      {
1003      Object lhs = this.elements.get( i );
1004      Object rhs = other.elements.get( i );
1005
1006      if( lhs == null && rhs == null )
1007        continue;
1008
1009      if( lhs == null || rhs == null )
1010        return false;
1011
1012      if( !lhs.equals( rhs ) )
1013        return false;
1014      }
1015
1016    return true;
1017    }
1018
1019  @Override
1020  public int hashCode()
1021    {
1022    int hash = 1;
1023
1024    for( Object element : elements )
1025      hash = 31 * hash + ( element != null ? element.hashCode() : 0 );
1026
1027    return hash;
1028    }
1029
1030  @Override
1031  public String toString()
1032    {
1033    return Util.join( elements, printDelim, true );
1034    }
1035
1036  /**
1037   * Method toString writes this Tuple instance values out to a String delimited by the given String value.
1038   *
1039   * @param delim of type String
1040   * @return String
1041   */
1042  public String toString( String delim )
1043    {
1044    return Util.join( elements, delim, true );
1045    }
1046
1047  /**
1048   * Method toString writes this Tuple instance values out to a String delimited by the given String value.
1049   *
1050   * @param delim     of type String
1051   * @param printNull of type boolean
1052   * @return String
1053   */
1054  public String toString( String delim, boolean printNull )
1055    {
1056    return Util.join( elements, delim, printNull );
1057    }
1058
1059  /**
1060   * Method format uses the {@link Formatter} class for formatting this tuples values into a new string.
1061   *
1062   * @param format of type String
1063   * @return String
1064   */
1065  public String format( String format )
1066    {
1067    return String.format( format, elements() );
1068    }
1069
1070  /**
1071   * Method print returns a parsable String representation of this Tuple instance.
1072   *
1073   * @return String
1074   */
1075  public String print()
1076    {
1077    return printTo( new StringBuffer() ).toString();
1078    }
1079
1080  public StringBuffer printTo( StringBuffer buffer )
1081    {
1082    buffer.append( "[" );
1083
1084    if( elements != null )
1085      {
1086      for( int i = 0; i < elements.size(); i++ )
1087        {
1088        Object element = elements.get( i );
1089
1090        if( element instanceof Tuple )
1091          ( (Tuple) element ).printTo( buffer );
1092        else if( element == null ) // don't quote nulls to distinguish from null strings
1093          buffer.append( element );
1094        else
1095          buffer.append( "\'" ).append( element ).append( "\'" );
1096
1097        if( i < elements.size() - 1 )
1098          buffer.append( ", " );
1099        }
1100      }
1101
1102    buffer.append( "]" );
1103
1104    return buffer;
1105    }
1106
1107  private final void verifyModifiable()
1108    {
1109    if( isUnmodifiable )
1110      throw new UnsupportedOperationException( "this tuple is unmodifiable" );
1111    }
1112  }