/* * Copyright (c) 2007-2010 Concurrent, Inc. All Rights Reserved. * * Project and contact information: http://www.cascading.org/ * * This file is part of the Cascading project. * * Cascading is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Cascading is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Cascading. If not, see <http://www.gnu.org/licenses/>. */ package cascading.tuple; import java.beans.ConstructorProperties; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import cascading.pipe.Group; import cascading.tap.Tap; import cascading.util.Util; /** * Class Fields represents the field names in a {@link Tuple}. A tuple field may be a literal String value representing a * name, or it may be a literal Integer value representing a position, where positions start at position 0. * A Fields instance may also represent a set of field names and positions. * <p/> * Fields are used as both declarators and selectors. A declarator declares that a given {@link Tap} or * {@link cascading.operation.Operation} returns the given field names, for a set of values the size of * the given Fields instance. A selector is used to select given referenced fields from a Tuple. * For example; <br/> * <code>Fields fields = new Fields( "a", "b", "c" );</code><br/> * This creates a new Fields instance with the field names "a", "b", and "c". This Fields instance can be used as both * a declarator or a selector, depending on how it's used. * <p/> * Or For example; <br/> * <code>Fields fields = new Fields( 1, 2, -1 );</code><br/> * This creates a new Fields instance that can only be used as a selector. It would select the second, third, and last * position from a given Tuple instance, assuming it has at least four positions. Since the original field names for those * positions will carry over to the new selected Tuple instance, if the original Tuple only had three positions, the third * and last positions would be the same, and would throw an error on there being duplicate field names in the selected * Tuple instance. * <p/> * Additionally, there are eight predefined Fields sets used for different purposes; {@link #ALL}, {@link #GROUP}, * {@link #VALUES}, {@link #ARGS}, {@link #RESULTS}, {@link #UNKNOWN}, {@link #REPLACE}, and {@link #SWAP}. * <p/> * The {@code ALL} Fields set is a "wildcard" that represents all the current available fields. * <p/> * The {@code GROUP} Fields set represents all the fields used as grouping values in a previous {@link Group}. * If there is no previous Group in the pipe assembly, the GROUP represents all the current field names. * <p/> * The {@code VALUES} Fields set represent all the fields not used as grouping fields in a previous Group. * <p/> * The {@code ARGS} Fields set is used to let a given Operation inherit the field names of its argument Tuple. This Fields set * is a convenience and is typically used when the Pipe output selector is {@code RESULTS} or {@code REPLACE}. * <p/> * The {@code RESULTS} Fields set is used to represent the field names of the current Operations return values. This Fields * set may only be used as an output selector on a Pipe. It effectively replaces in the input Tuple with the Operation result * Tuple. * <p/> * The {@code UNKNOWN} Fields set is used when Fields must be declared, but how many and their names is unknown. This allows * for arbitrarily length Tuples from an input source or some Operation. Use this Fields set with caution. * <p/> * The {@code REPLACE} Fields set is used as an output selector to inline replace values in the incoming Tuple with * the results of an Operation. This is a convenience Fields set that allows subsequent Operations to 'step' on the * value with a given field name. The current Operation must always use the exact same field names, or the {@code ARGS} * Fields set. * <p/> * The {@code SWAP} Fields set is used as an output selector to swap out Operation arguments with its results. Neither * the argument and result field names or size need to be the same. This is useful for when the Operation arguments are * no longer necessary and the result Fields and values should be appended to the remainder of the input field names * and Tuple. */ public class Fields implements Comparable, Iterable<Comparable>, Serializable, Comparator<Tuple> { /** Field UNKNOWN */ public static final Fields UNKNOWN = new Fields( Kind.UNKNOWN ); /** Field ALL represents a wildcard for all fields */ public static final Fields ALL = new Fields( Kind.ALL ); /** Field KEYS represents all fields used as they key for the last grouping */ public static final Fields GROUP = new Fields( Kind.GROUP ); /** Field VALUES represents all fields used as values for the last grouping */ public static final Fields VALUES = new Fields( Kind.VALUES ); /** Field ARGS represents all fields used as the arguments for the current operation */ public static final Fields ARGS = new Fields( Kind.ARGS ); /** Field RESULTS represents all fields returned by the current operation */ public static final Fields RESULTS = new Fields( Kind.RESULTS ); /** Field REPLACE represents all incoming fields, and allows their values to be replaced by the current operation results. */ public static final Fields REPLACE = new Fields( Kind.REPLACE ); /** Field SWAP represents all fields not used as arguments for the current operation and the operations results. */ public static final Fields SWAP = new Fields( Kind.SWAP ); /** Field FIRST represents the first field position, 0 */ public static final Fields FIRST = new Fields( 0 ); /** Field LAST represents the last field position, -1 */ public static final Fields LAST = new Fields( -1 ); /** Field EMPTY_INT */ private static final int[] EMPTY_INT = new int[ 0 ]; /** */ static enum Kind { ALL, GROUP, VALUES, ARGS, RESULTS, UNKNOWN, REPLACE, SWAP; } /** Field fields */ Comparable[] fields = new Comparable[ 0 ]; /** Field isOrdered */ boolean isOrdered = true; /** Field kind */ Kind kind; /** Field comparators */ Comparator[] comparators; /** Field thisPos */ transient int[] thisPos; /** Field index */ transient Map<Comparable, Integer> index; /** Field posCache */ transient Map<Fields, int[]> posCache; /** Field hashCode */ transient int hashCode; // need to cache this /** * Method fields is a convenience method to create an array of Fields instances. * * @param fields of type Fields * @return Fields[] */ public static Fields[] fields( Fields... fields ) { return fields; } /** * Method size is a factory that makes new instances of Fields the given size. * * @param size of type int * @return Fields */ public static Fields size( int size ) { Fields fields = new Fields(); fields.fields = expand( size, 0 ); return fields; } /** * Method join joins all given Fields instances into a new Fields instance. * <p/> * Use caution with this method, it does not assume the given Fields are either selectors or declarators. Numeric position fields are left untouched. * * @param fields of type Fields * @return Fields */ public static Fields join( Fields... fields ) { int size = 0; for( Fields field : fields ) { if( field.isSubstitution() || field.isUnknown() ) throw new TupleException( "cannot join fields if one is a substitution or is unknown" ); size += field.size(); } Comparable[] elements = join( size, fields ); return new Fields( elements ); } private static Comparable[] join( int size, Fields... fields ) { Comparable[] elements = expand( size, 0 ); int pos = 0; for( Fields field : fields ) { System.arraycopy( field.fields, 0, elements, pos, field.size() ); pos += field.size(); } return elements; } /** * Method merge merges all given Fields instances into a new Fields instance where a merge is a set union of all the * given Fields instances. * <p/> * Thus duplicate positions or field names are allowed, they are subsequently discarded in favor of the first * occurrence. That is, merging "a" and "a" would yield "a", not "a, a", yet merging "a,b" and "c" would yield "a,b,c". * <p/> * Use caution with this method, it does not assume the given Fields are either selectors or declarators. Numeric position fields are left untouched. * * @param fields of type Fields * @return Fields */ public static Fields merge( Fields... fields ) { List<Comparable> elements = new ArrayList<Comparable>(); for( Fields field : fields ) { for( Comparable comparable : field ) { if( !elements.contains( comparable ) ) elements.add( comparable ); } } return new Fields( elements.toArray( new Comparable[ elements.size() ] ) ); } /** * Method offsetSelector is a factory that makes new instances of Fields the given size but offset by startPos. * The result Fields instance can only be used as a selector. * * @param size of type int * @param startPos of type int * @return Fields */ public static Fields offsetSelector( int size, int startPos ) { Fields fields = new Fields(); fields.isOrdered = false; fields.fields = expand( size, startPos ); return fields; } private static Comparable[] expand( int size, int startPos ) { if( size < 1 ) throw new TupleException( "invalid size for fields: " + size ); if( startPos < 0 ) throw new TupleException( "invalid start position for fields: " + startPos ); Comparable[] fields = new Comparable[ size ]; for( int i = 0; i < fields.length; i++ ) fields[ i ] = i + startPos; return fields; } /** * Method resolve returns a new selector expanded on the given field declarations * * @param selector of type Fields * @param fields of type Fields * @return Fields */ public static Fields resolve( Fields selector, Fields... fields ) { boolean hasUnknowns = false; int size = 0; for( Fields field : fields ) { if( field.isUnknown() ) hasUnknowns = true; if( !field.isDefined() && field.isUnOrdered() ) throw new TupleException( "unable to select from field set: " + field.printVerbose() ); size += field.size(); } if( selector.isAll() ) { Fields result = fields[ 0 ]; for( int i = 1; i < fields.length; i++ ) result = result.append( fields[ i ] ); return result; } if( selector.isReplace() ) { if( fields[ 1 ].isUnknown() ) throw new TupleException( "cannot replace fields with unknown field declaration" ); if( !fields[ 0 ].contains( fields[ 1 ] ) ) throw new TupleException( "could not find all fields to be replaced, available: " + fields[ 0 ].printVerbose() + ", declared: " + fields[ 1 ].printVerbose() ); return fields[ 0 ]; } // we can't deal with anything but ALL if( !selector.isDefined() ) throw new TupleException( "unable to use given selector: " + selector ); Set<String> notFound = new LinkedHashSet<String>(); Set<String> found = new HashSet<String>(); Fields result = size( selector.size() ); if( hasUnknowns ) size = -1; int offset = 0; for( Fields current : fields ) { resolveInto( notFound, found, selector, current, result, offset, size ); offset += current.size(); } notFound.removeAll( found ); if( !notFound.isEmpty() ) throw new FieldsResolverException( new Fields( join( size, fields ) ), new Fields( notFound.toArray( new Comparable[ 0 ] ) ) ); if( hasUnknowns ) return selector; return result; } private static void resolveInto( Set<String> notFound, Set<String> found, Fields selector, Fields current, Fields result, int offset, int size ) { for( int i = 0; i < selector.size(); i++ ) { Comparable field = selector.get( i ); if( field instanceof String ) { int index = current.indexOfSafe( field ); if( index == -1 ) notFound.add( (String) field ); else result.set( i, handleFound( found, field ) ); continue; } int pos = current.translatePos( (Integer) field, size ) - offset; if( pos >= current.size() || pos < 0 ) continue; Comparable thisField = current.get( pos ); if( thisField instanceof String ) result.set( i, handleFound( found, thisField ) ); else result.set( i, field ); } } private static Comparable handleFound( Set<String> found, Comparable field ) { if( found.contains( (String) field ) ) throw new TupleException( "field name already exists: " + field ); found.add( (String) field ); return field; } /** * Method asDeclaration returns a new Fields instance for use as a declarator based on the given fields value. * <p/> * Typically this is used to convert a selector to a declarator. Simply, all numeric position fields are replaced * by their absolute position. * * @param fields of type Fields * @return Fields */ public static Fields asDeclaration( Fields fields ) { if( !fields.isDefined() ) return UNKNOWN; if( fields.isOrdered() ) return fields; Fields result = size( fields.size() ); copy( null, result, fields, 0 ); return result; } private static Fields asSelector( Fields fields ) { if( !fields.isDefined() ) return UNKNOWN; return fields; } private Fields() { } /** * Constructor Fields creates a new Fields instance. * * @param kind of type Kind */ @SuppressWarnings({"SameParameterValue"}) protected Fields( Kind kind ) { this.kind = kind; } /** * Constructor Fields creates a new Fields instance. * * @param fields of type Comparable... */ @ConstructorProperties({"fields"}) public Fields( Comparable... fields ) { this.fields = validate( fields ); } /** * Method isUnOrdered returns true if this instance is unordered. That is, it has relative numeric field positions. * For example; [1,"a",2,-1] * * @return the unOrdered (type boolean) of this Fields object. */ public boolean isUnOrdered() { return !isOrdered || kind == Kind.ALL; } /** * Method isOrdered returns true if this instance is orderd. That is, all numeric field positions are absolute. * For example; [0,"a",2,3] * * @return the ordered (type boolean) of this Fields object. */ public boolean isOrdered() { return isOrdered || kind == Kind.UNKNOWN; } /** * Method isDefined returns true if this instance is not a field set like {@link #ALL} or {@link #UNKNOWN}. * * @return the defined (type boolean) of this Fields object. */ public boolean isDefined() { return kind == null; } /** * Method isOutSelector returns true if this instance is 'defined', or the field set {@link #ALL} or {@link #RESULTS}. * * @return the outSelector (type boolean) of this Fields object. */ public boolean isOutSelector() { return isAll() || isResults() || isReplace() || isSwap() || isDefined(); } /** * Method isArgSelector returns true if this instance is 'defined' or the field set {@link #ALL}, {@link #GROUP}, or * {@link #VALUES}. * * @return the argSelector (type boolean) of this Fields object. */ public boolean isArgSelector() { return isAll() || isGroup() || isValues() || isDefined(); } /** * Method isDeclarator returns true if this can be used as a declarator. Specifically if it is 'defined' or * {@link #UNKNOWN}, {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}. * * @return the declarator (type boolean) of this Fields object. */ public boolean isDeclarator() { return isUnknown() || isAll() || isArguments() || isGroup() || isValues() || isDefined(); } /** * Method isAll returns true if this instance is the {@link #ALL} field set. * * @return the all (type boolean) of this Fields object. */ public boolean isAll() { return kind == Kind.ALL; } /** * Method isUnknown returns true if this instance is the {@link #UNKNOWN} field set. * * @return the unknown (type boolean) of this Fields object. */ public boolean isUnknown() { return kind == Kind.UNKNOWN; } /** * Method isArguments returns true if this instance is the {@link #ARGS} field set. * * @return the arguments (type boolean) of this Fields object. */ public boolean isArguments() { return kind == Kind.ARGS; } /** * Method isValues returns true if this instance is the {@link #VALUES} field set. * * @return the values (type boolean) of this Fields object. */ public boolean isValues() { return kind == Kind.VALUES; } /** * Method isResults returns true if this instance is the {@link #RESULTS} field set. * * @return the results (type boolean) of this Fields object. */ public boolean isResults() { return kind == Kind.RESULTS; } /** * Method isReplace returns true if this instance is the {@link #REPLACE} field set. * * @return the replace (type boolean) of this Fields object. */ public boolean isReplace() { return kind == Kind.REPLACE; } /** * Method isSwap returns true if this instance is the {@link #SWAP} field set. * * @return the swap (type boolean) of this Fields object. */ public boolean isSwap() { return kind == Kind.SWAP; } /** * Method isKeys returns true if this instance is the {@link #GROUP} field set. * * @return the keys (type boolean) of this Fields object. */ public boolean isGroup() { return kind == Kind.GROUP; } /** * Method isSubstitution returns true if this instance is a substitution fields set. Specifically if it is the field * set {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}. * * @return the substitution (type boolean) of this Fields object. */ public boolean isSubstitution() { return isAll() || isArguments() || isGroup() || isValues(); } private Comparable[] validate( Comparable[] fields ) { isOrdered = true; Set<Comparable> names = new HashSet<Comparable>(); for( int i = 0; i < fields.length; i++ ) { Comparable field = fields[ i ]; if( field == null ) throw new IllegalArgumentException( "field name or position may not be null" ); if( field instanceof Fields ) throw new IllegalArgumentException( "may not nest Fields instances within a Fields instance" ); if( names.contains( field ) ) throw new IllegalArgumentException( "duplicate field name found: " + field ); names.add( field ); if( field instanceof Number && (Integer) field != i ) isOrdered = false; } return fields; } final Comparable[] get() { return fields; } /** * Method get returns the field name or position at the given index i. * * @param i is of type int * @return Comparable */ public final Comparable get( int i ) { return fields[ i ]; } final void set( int i, Comparable comparable ) { fields[ i ] = comparable; if( isOrdered() && comparable instanceof Integer ) isOrdered = i == (Integer) comparable; } /** * Method getPos returns the pos array of this Fields object. * * @return the pos (type int[]) of this Fields object. */ public int[] getPos() { if( thisPos != null ) return thisPos; // do not clone if( isAll() || isUnknown() ) thisPos = EMPTY_INT; else thisPos = makeThisPos(); return thisPos; } private int[] makeThisPos() { int[] pos = new int[ size() ]; for( int i = 0; i < size(); i++ ) { Comparable field = get( i ); if( field instanceof Number ) pos[ i ] = (Integer) field; else pos[ i ] = i; } return pos; } private final Map<Fields, int[]> getPosCache() { if( posCache == null ) posCache = new HashMap<Fields, int[]>(); return posCache; } private final int[] putReturn( Fields fields, int[] pos ) { getPosCache().put( fields, pos ); return pos; } final int[] getPos( Fields fields ) { return getPos( fields, -1 ); } final int[] getPos( Fields fields, int tupleSize ) { // test for key, as we stuff a null value if( !isUnknown() && getPosCache().containsKey( fields ) ) return getPosCache().get( fields ); if( fields.isAll() ) return putReturn( fields, null ); // return null, not getPos() if( isAll() ) return putReturn( fields, fields.getPos() ); // don't cache unknown if( size() == 0 && isUnknown() ) return translatePos( fields, tupleSize ); int[] pos = translatePos( fields, size() ); return putReturn( fields, pos ); } private int[] translatePos( Fields fields, int fieldSize ) { int[] pos = new int[ fields.size() ]; for( int i = 0; i < fields.size(); i++ ) { Comparable field = fields.get( i ); if( field instanceof Number ) pos[ i ] = translatePos( (Integer) field, fieldSize ); else pos[ i ] = indexOf( field ); } return pos; } final int translatePos( Integer integer ) { return translatePos( integer, size() ); } final int translatePos( Integer integer, int size ) { if( size == -1 ) return integer; if( integer < 0 ) integer = size + integer; if( !isUnknown() && ( integer >= size || integer < 0 ) ) throw new TupleException( "position value is too large: " + integer + ", positions in field: " + size ); return integer; } /** * Method getPos returns the index of the give field value in this Fields instance. The index corresponds to the * Tuple value index in an associated Tuple instance. * * @param fieldName of type Comparable * @return int */ public int getPos( Comparable fieldName ) { if( fieldName instanceof Number ) return translatePos( (Integer) fieldName ); else return indexOf( fieldName ); } private final Map<Comparable, Integer> getIndex() { if( index != null ) return index; // make thread-safe by not having invalid intermediate state Map<Comparable, Integer> local = new HashMap<Comparable, Integer>(); for( int i = 0; i < size(); i++ ) local.put( get( i ), i ); return index = local; } private int indexOf( Comparable fieldName ) { Integer result = getIndex().get( fieldName ); if( result == null ) throw new FieldsResolverException( this, new Fields( fieldName ) ); return result; } int indexOfSafe( Comparable fieldName ) { Integer result = getIndex().get( fieldName ); if( result == null ) return -1; return result; } /** * Method iterator return an unmodifiable iterator of field values. if {@link #isSubstitution()} returns true, * this iterator will be empty. * * @return Iterator */ public Iterator iterator() { return Collections.unmodifiableList( Arrays.asList( fields ) ).iterator(); } /** * Method select returns a new Fields instance with fields specified by the given selector. * * @param selector of type Fields * @return Fields */ public Fields select( Fields selector ) { if( !isOrdered() ) throw new TupleException( "this fields instance can only be used as a selector" ); if( selector.isAll() ) return this; // supports -1_UNKNOWN_RETURNED // guarantees pos arguments remain selector positions, not absolute positions if( isUnknown() ) return asSelector( selector ); Fields result = size( selector.size() ); // todo: this can be cleaned up i think for( int i = 0; i < selector.size(); i++ ) { Comparable field = selector.get( i ); if( field instanceof String ) result.set( i, get( indexOf( field ) ) ); else if( this.get( translatePos( (Integer) field ) ) instanceof String ) result.set( i, get( translatePos( (Integer) field ) ) ); else result.set( i, translatePos( (Integer) field ) ); // use absolute position if no field name } return result; } /** * Method subtract returns the difference between this instance and the given fields instance. * <p/> * See {@link #append(Fields)} for adding field names. * * @param fields of type Fields * @return Fields */ public Fields subtract( Fields fields ) { Fields minus = new Fields(); if( fields.isAll() ) return minus; List<Comparable> list = new LinkedList<Comparable>(); Collections.addAll( list, this.get() ); int[] pos = getPos( fields, -1 ); for( int i : pos ) list.set( i, null ); Util.removeAllNulls( list ); minus.fields = list.toArray( new Comparable[ list.size() ] ); return minus; } /** * Method is used for appending the given Fields instance to this instance, into a new Fields instance. * <p/> * See {@link #subtract(Fields)} for removing field names. * * @param fields of type Fields[] * @return Fields */ public Fields append( Fields[] fields ) { if( fields.length == 0 ) return null; Fields field = fields[ 0 ]; for( int i = 1; i < fields.length; i++ ) field = field.append( fields[ i ] ); return field; } /** * Method is used for appending the given Fields instance to this instance, into a new Fields instance. * <p/> * See {@link #subtract(Fields)} for removing field names. * * @param fields of type Fields * @return Fields */ public Fields append( Fields fields ) { if( fields == null ) return this; // allow unordered fields to be appended to build more complex selectors if( this.isAll() || fields.isAll() ) throw new TupleException( "cannot append fields: " + this.print() + " + " + fields.print() ); if( ( this.isUnknown() || this.size() == 0 ) && fields.isUnknown() ) return UNKNOWN; Set<String> names = new HashSet<String>(); // init the Field Fields result = size( this.size() + fields.size() ); // copy over field names from this side copy( names, result, this, 0 ); // copy over field names from that side copy( names, result, fields, this.size() ); if( this.isUnknown() || fields.isUnknown() ) result.kind = Kind.UNKNOWN; return result; } /** * Method rename will rename the from fields to the values in to to fields. Fields may contain field names, or * positions. * <p/> * Using positions is useful to remove a field name put keep its place in the Tuple stream. * * @param from of type Fields * @param to of type Fields * @return Fields */ public Fields rename( Fields from, Fields to ) { if( this.isSubstitution() || this.isUnknown() ) throw new TupleException( "cannot rename fields in a substitution or unkown Fields instance: " + this.print() ); if( from.size() != to.size() ) throw new TupleException( "from and to fields must be the same size" ); if( from.isSubstitution() || from.isUnknown() ) throw new TupleException( "from fields may not be a substitution or unknown" ); if( to.isSubstitution() || to.isUnknown() ) throw new TupleException( "to fields may not be a substitution or unknown" ); Set<String> names = new HashSet<String>(); Comparable[] newFields = Arrays.copyOf( this.fields, this.fields.length ); int[] pos = getPos( from ); for( int i = 0; i < pos.length; i++ ) newFields[ pos[ i ] ] = to.fields[ i ]; return new Fields( newFields ); } /** * Method project will return a new Fields instance similar to the given fields instance * except any absolute positional elements will be replaced by the current field names, if any. * * @param fields of type Fields * @return Fields */ public Fields project( Fields fields ) { if( fields == null ) return this; Fields results = size( fields.size() ); for( int i = 0; i < fields.fields.length; i++ ) { if( fields.fields[ i ] instanceof String ) results.fields[ i ] = fields.fields[ i ]; else if( this.fields[ i ] instanceof String ) results.fields[ i ] = this.fields[ i ]; else results.fields[ i ] = i; } return results; } private static void copy( Set<String> names, Fields result, Fields fields, int offset ) { for( int i = 0; i < fields.size(); i++ ) { Comparable field = fields.get( i ); if( !( field instanceof String ) ) continue; if( names != null ) { if( names.contains( (String) field ) ) throw new TupleException( "field name already exists: " + field ); names.add( (String) field ); } result.set( i + offset, field ); } } /** * Method verifyContains tests if this instance contains the field names and positions specified in the given * fields instance. If the test fails, a {@link TupleException} is thrown. * * @param fields of type Fields * @throws TupleException when one or more fields are not contained in this instance. */ public void verifyContains( Fields fields ) { if( isUnknown() ) return; try { getPos( fields ); } catch( TupleException exception ) { throw new TupleException( "these fields " + print() + ", do not contain " + fields.print() ); } } /** * Method contains returns true if this instance contains the field names and positions specified in the given * fields instance. * * @param fields of type Fields * @return boolean */ public boolean contains( Fields fields ) { try { getPos( fields ); return true; } catch( Exception exception ) { return false; } } /** * Method compareTo compares this instance to the given Fields instance. * * @param other of type Fields * @return int */ public int compareTo( Fields other ) { if( other.size() != size() ) return other.size() < size() ? 1 : -1; for( int i = 0; i < size(); i++ ) { int c = get( i ).compareTo( other.get( i ) ); if( c != 0 ) return c; } return 0; } /** * Method compareTo implements {@link Comparable#compareTo(Object)}. * * @param other of type Object * @return int */ public int compareTo( Object other ) { if( other instanceof Fields ) return compareTo( (Fields) other ); else return -1; } /** * Method print returns a String representation of this instance. * * @return String */ public String print() { return "[" + toString() + "]"; } /** * Method printLong returns a String representation of this instance along with the size. * * @return String */ public String printVerbose() { return "[{" + ( isDefined() ? size() : "?" ) + "}:" + toString() + "]"; } @Override public String toString() { if( isOrdered() ) return orderedToString(); else return unorderedToString(); } private String orderedToString() { StringBuffer buffer = new StringBuffer(); if( size() != 0 ) { int startIndex = get( 0 ) instanceof Number ? (Integer) get( 0 ) : 0; for( int i = 0; i < size(); i++ ) { Comparable field = get( i ); if( field instanceof Number ) { if( i + 1 == size() || !( get( i + 1 ) instanceof Number ) ) { if( buffer.length() != 0 ) buffer.append( ", " ); if( startIndex != i ) buffer.append( startIndex ).append( ":" ).append( field ); else buffer.append( i ); startIndex = i; } continue; } if( i != 0 ) buffer.append( ", " ); if( field instanceof String ) buffer.append( "\'" ).append( field ).append( "\'" ); else if( field instanceof Fields ) buffer.append( ( (Fields) field ).print() ); startIndex = i + 1; } } if( kind != null ) { if( buffer.length() != 0 ) buffer.append( ", " ); buffer.append( kind ); } return buffer.toString(); } private String unorderedToString() { StringBuffer buffer = new StringBuffer(); for( Object field : get() ) { if( buffer.length() != 0 ) buffer.append( ", " ); if( field instanceof String ) buffer.append( "\'" ).append( field ).append( "\'" ); else if( field instanceof Fields ) buffer.append( ( (Fields) field ).print() ); else buffer.append( field ); } if( kind != null ) { if( buffer.length() != 0 ) buffer.append( ", " ); buffer.append( kind ); } return buffer.toString(); } /** * Method size returns the number of field positions in this instance. * * @return int */ public final int size() { return fields.length; } /** * Method setComparator should be used to associate a {@link java.util.Comparator} with a given field name or position. * <br/> * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will * be considered. * * @param fieldName of type Comparable * @param comparator of type Comparator */ public void setComparator( Comparable fieldName, Comparator comparator ) { if( !( comparator instanceof Serializable ) ) throw new IllegalArgumentException( "given comparator must be serializable" ); if( comparators == null ) comparators = new Comparator[ size() ]; try { comparators[ getPos( asFieldName( fieldName ) ) ] = comparator; } catch( FieldsResolverException exception ) { throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception ); } } /** * Method setComparators sets all the comparators of this FieldsComparator object. The Comparator array * must be the same length as the number for fields in this instance. * * @param comparators the comparators of this FieldsComparator object. */ public void setComparators( Comparator... comparators ) { if( comparators.length != size() ) throw new IllegalArgumentException( "given number of comparator instances must match fields size" ); for( Comparator comparator : comparators ) { if( !( comparator instanceof Serializable ) ) throw new IllegalArgumentException( "comparators must be serializable" ); } this.comparators = comparators; } private Comparable asFieldName( Comparable fieldName ) { if( fieldName instanceof Fields ) { Fields fields = (Fields) fieldName; if( !fields.isDefined() ) throw new TupleException( "given Fields instance must explicitly declare one field name or position: " + fields.printVerbose() ); fieldName = fields.get( 0 ); } return fieldName; } /** * Method getComparators returns the comparators of this Fields object. * * @return the comparators (type Comparator[]) of this Fields object. */ public Comparator[] getComparators() { Comparator[] copy = new Comparator[ size() ]; System.arraycopy( comparators, 0, copy, 0, size() ); return copy; } /** * Method hasComparators test if this Fields instance has Comparators. * * @return boolean */ public boolean hasComparators() { return comparators != null; } @Override public int compare( Tuple lhs, Tuple rhs ) { return lhs.compareTo( comparators, rhs ); } @Override public boolean equals( Object object ) { if( this == object ) return true; if( object == null || getClass() != object.getClass() ) return false; Fields fields1 = (Fields) object; return this.kind == fields1.kind && Arrays.equals( get(), fields1.get() ); } @Override public int hashCode() { if( hashCode == 0 ) hashCode = get() != null ? Arrays.hashCode( get() ) : 0; return hashCode; } }