001 /* 002 * Copyright (c) 2007-2014 Concurrent, Inc. All Rights Reserved. 003 * 004 * Project and contact information: http://www.cascading.org/ 005 * 006 * This file is part of the Cascading project. 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020 021 package cascading.tuple; 022 023 import java.beans.ConstructorProperties; 024 import java.io.Serializable; 025 import java.lang.reflect.Type; 026 import java.util.ArrayList; 027 import java.util.Arrays; 028 import java.util.Collections; 029 import java.util.Comparator; 030 import java.util.HashMap; 031 import java.util.HashSet; 032 import java.util.Iterator; 033 import java.util.LinkedHashSet; 034 import java.util.LinkedList; 035 import java.util.List; 036 import java.util.Map; 037 import java.util.Set; 038 039 import cascading.tap.Tap; 040 import cascading.tuple.type.CoercibleType; 041 import cascading.util.Util; 042 043 /** 044 * Class Fields represents the field names in a {@link Tuple}. A tuple field may be a literal String value representing a 045 * name, or it may be a literal Integer value representing a position, where positions start at position 0. 046 * A Fields instance may also represent a set of field names and positions. 047 * <p/> 048 * Fields are used as both declarators and selectors. A declarator declares that a given {@link Tap} or 049 * {@link cascading.operation.Operation} returns the given field names, for a set of values the size of 050 * the given Fields instance. A selector is used to select given referenced fields from a Tuple. 051 * For example; <br/> 052 * <code>Fields fields = new Fields( "a", "b", "c" );</code><br/> 053 * This creates a new Fields instance with the field names "a", "b", and "c". This Fields instance can be used as both 054 * a declarator or a selector, depending on how it's used. 055 * <p/> 056 * Or For example; <br/> 057 * <code>Fields fields = new Fields( 1, 2, -1 );</code><br/> 058 * This creates a new Fields instance that can only be used as a selector. It would select the second, third, and last 059 * position from a given Tuple instance, assuming it has at least four positions. Since the original field names for those 060 * positions will carry over to the new selected Tuple instance, if the original Tuple only had three positions, the third 061 * and last positions would be the same, and would throw an error on there being duplicate field names in the selected 062 * Tuple instance. 063 * <p/> 064 * Additionally, there are eight predefined Fields sets used for different purposes; {@link #NONE}, {@link #ALL}, {@link #GROUP}, 065 * {@link #VALUES}, {@link #ARGS}, {@link #RESULTS}, {@link #UNKNOWN}, {@link #REPLACE}, and {@link #SWAP}. 066 * <p/> 067 * The {@code NONE} Fields set represents no fields. 068 * <p/> 069 * The {@code ALL} Fields set is a "wildcard" that represents all the current available fields. 070 * <p/> 071 * The {@code GROUP} Fields set represents all the fields used as grouping values in a previous {@link cascading.pipe.Splice}. 072 * If there is no previous Group in the pipe assembly, the GROUP represents all the current field names. 073 * <p/> 074 * The {@code VALUES} Fields set represent all the fields not used as grouping fields in a previous Group. 075 * <p/> 076 * The {@code ARGS} Fields set is used to let a given Operation inherit the field names of its argument Tuple. This Fields set 077 * is a convenience and is typically used when the Pipe output selector is {@code RESULTS} or {@code REPLACE}. 078 * <p/> 079 * The {@code RESULTS} Fields set is used to represent the field names of the current Operations return values. This Fields 080 * set may only be used as an output selector on a Pipe. It effectively replaces in the input Tuple with the Operation result 081 * Tuple. 082 * <p/> 083 * The {@code UNKNOWN} Fields set is used when Fields must be declared, but how many and their names is unknown. This allows 084 * for arbitrarily length Tuples from an input source or some Operation. Use this Fields set with caution. 085 * <p/> 086 * The {@code REPLACE} Fields set is used as an output selector to inline replace values in the incoming Tuple with 087 * the results of an Operation. This is a convenience Fields set that allows subsequent Operations to 'step' on the 088 * value with a given field name. The current Operation must always use the exact same field names, or the {@code ARGS} 089 * Fields set. 090 * <p/> 091 * The {@code SWAP} Fields set is used as an output selector to swap out Operation arguments with its results. Neither 092 * the argument and result field names or size need to be the same. This is useful for when the Operation arguments are 093 * no longer necessary and the result Fields and values should be appended to the remainder of the input field names 094 * and Tuple. 095 */ 096 public class Fields implements Comparable, Iterable<Comparable>, Serializable, Comparator<Tuple> 097 { 098 /** Field UNKNOWN */ 099 public static final Fields UNKNOWN = new Fields( Kind.UNKNOWN ); 100 /** Field NONE represents a wildcard for no fields */ 101 public static final Fields NONE = new Fields( Kind.NONE ); 102 /** Field ALL represents a wildcard for all fields */ 103 public static final Fields ALL = new Fields( Kind.ALL ); 104 /** Field KEYS represents all fields used as they key for the last grouping */ 105 public static final Fields GROUP = new Fields( Kind.GROUP ); 106 /** Field VALUES represents all fields used as values for the last grouping */ 107 public static final Fields VALUES = new Fields( Kind.VALUES ); 108 /** Field ARGS represents all fields used as the arguments for the current operation */ 109 public static final Fields ARGS = new Fields( Kind.ARGS ); 110 /** Field RESULTS represents all fields returned by the current operation */ 111 public static final Fields RESULTS = new Fields( Kind.RESULTS ); 112 /** Field REPLACE represents all incoming fields, and allows their values to be replaced by the current operation results. */ 113 public static final Fields REPLACE = new Fields( Kind.REPLACE ); 114 /** Field SWAP represents all fields not used as arguments for the current operation and the operations results. */ 115 public static final Fields SWAP = new Fields( Kind.SWAP ); 116 /** Field FIRST represents the first field position, 0 */ 117 public static final Fields FIRST = new Fields( 0 ); 118 /** Field LAST represents the last field position, -1 */ 119 public static final Fields LAST = new Fields( -1 ); 120 121 /** Field EMPTY_INT */ 122 private static final int[] EMPTY_INT = new int[ 0 ]; 123 124 /** 125 */ 126 static enum Kind 127 { 128 NONE, ALL, GROUP, VALUES, ARGS, RESULTS, UNKNOWN, REPLACE, SWAP 129 } 130 131 /** Field fields */ 132 Comparable[] fields = new Comparable[ 0 ]; 133 /** Field isOrdered */ 134 boolean isOrdered = true; 135 /** Field kind */ 136 Kind kind; 137 138 /** Field types */ 139 Type[] types; 140 /** Field comparators */ 141 Comparator[] comparators; 142 143 /** Field thisPos */ 144 transient int[] thisPos; 145 /** Field index */ 146 transient Map<Comparable, Integer> index; 147 /** Field posCache */ 148 transient Map<Fields, int[]> posCache; 149 /** Field hashCode */ 150 transient int hashCode; // need to cache this 151 152 /** 153 * Method fields is a convenience method to create an array of Fields instances. 154 * 155 * @param fields of type Fields 156 * @return Fields[] 157 */ 158 public static Fields[] fields( Fields... fields ) 159 { 160 return fields; 161 } 162 163 public static Comparable[] names( Comparable... names ) 164 { 165 return names; 166 } 167 168 public static Type[] types( Type... types ) 169 { 170 return types; 171 } 172 173 /** 174 * Method size is a factory that makes new instances of Fields the given size. 175 * 176 * @param size of type int 177 * @return Fields 178 */ 179 public static Fields size( int size ) 180 { 181 if( size == 0 ) 182 return Fields.NONE; 183 184 Fields fields = new Fields(); 185 186 fields.fields = expand( size, 0 ); 187 188 return fields; 189 } 190 191 /** 192 * Method size is a factory that makes new instances of Fields the given size with every field 193 * of the given type. 194 * 195 * @param size of type int 196 * @param type of type Type 197 * @return Fields 198 */ 199 public static Fields size( int size, Type type ) 200 { 201 if( size == 0 ) 202 return Fields.NONE; 203 204 Fields fields = new Fields(); 205 206 fields.fields = expand( size, 0 ); 207 208 for( Comparable field : fields ) 209 fields.applyType( field, type ); 210 211 return fields; 212 } 213 214 /** 215 * Method join joins all given Fields instances into a new Fields instance. 216 * <p/> 217 * Use caution with this method, it does not assume the given Fields are either selectors or declarators. Numeric position fields are left untouched. 218 * <p/> 219 * If the resulting set of fields and ordinals is length zero, {@link Fields#NONE} will be returned. 220 * 221 * @param fields of type Fields 222 * @return Fields 223 */ 224 public static Fields join( Fields... fields ) 225 { 226 return join( false, fields ); 227 } 228 229 public static Fields join( boolean maskDuplicateNames, Fields... fields ) 230 { 231 int size = 0; 232 233 for( Fields field : fields ) 234 { 235 if( field.isSubstitution() || field.isUnknown() ) 236 throw new TupleException( "cannot join fields if one is a substitution or is unknown" ); 237 238 size += field.size(); 239 } 240 241 if( size == 0 ) 242 return Fields.NONE; 243 244 Comparable[] elements = join( size, fields ); 245 246 if( maskDuplicateNames ) 247 { 248 Set<String> names = new HashSet<String>(); 249 250 for( int i = elements.length - 1; i >= 0; i-- ) 251 { 252 Comparable element = elements[ i ]; 253 254 if( names.contains( element ) ) 255 elements[ i ] = i; 256 else if( element instanceof String ) 257 names.add( (String) element ); 258 } 259 } 260 261 Type[] types = joinTypes( size, fields ); 262 263 if( types == null ) 264 return new Fields( elements ); 265 else 266 return new Fields( elements, types ); 267 } 268 269 private static Comparable[] join( int size, Fields... fields ) 270 { 271 Comparable[] elements = expand( size, 0 ); 272 273 int pos = 0; 274 for( Fields field : fields ) 275 { 276 System.arraycopy( field.fields, 0, elements, pos, field.size() ); 277 pos += field.size(); 278 } 279 280 return elements; 281 } 282 283 private static Type[] joinTypes( int size, Fields... fields ) 284 { 285 Type[] elements = new Type[ size ]; 286 287 int pos = 0; 288 for( Fields field : fields ) 289 { 290 if( field.isNone() ) 291 continue; 292 293 if( field.types == null ) 294 return null; 295 296 System.arraycopy( field.types, 0, elements, pos, field.size() ); 297 pos += field.size(); 298 } 299 300 return elements; 301 } 302 303 public static Fields mask( Fields fields, Fields mask ) 304 { 305 Comparable[] elements = expand( fields.size(), 0 ); 306 307 System.arraycopy( fields.fields, 0, elements, 0, elements.length ); 308 309 for( int i = elements.length - 1; i >= 0; i-- ) 310 { 311 Comparable element = elements[ i ]; 312 313 if( element instanceof Integer ) 314 continue; 315 316 if( mask.getIndex().containsKey( element ) ) 317 elements[ i ] = i; 318 } 319 320 return new Fields( elements ); 321 } 322 323 /** 324 * Method merge merges all given Fields instances into a new Fields instance where a merge is a set union of all the 325 * given Fields instances. 326 * <p/> 327 * Thus duplicate positions or field names are allowed, they are subsequently discarded in favor of the first 328 * occurrence. That is, merging "a" and "a" would yield "a", not "a, a", yet merging "a,b" and "c" would yield "a,b,c". 329 * <p/> 330 * Use caution with this method, it does not assume the given Fields are either selectors or declarators. Numeric position fields are left untouched. 331 * 332 * @param fields of type Fields 333 * @return Fields 334 */ 335 public static Fields merge( Fields... fields ) 336 { 337 List<Comparable> elements = new ArrayList<Comparable>(); 338 List<Type> elementTypes = new ArrayList<Type>(); 339 340 for( Fields field : fields ) 341 { 342 Type[] types = field.getTypes(); 343 int i = 0; 344 345 for( Comparable comparable : field ) 346 { 347 if( !elements.contains( comparable ) ) 348 { 349 elements.add( comparable ); 350 elementTypes.add( types == null ? null : types[ i ] ); // nulls ok 351 } 352 353 i++; 354 } 355 } 356 357 Comparable[] comparables = elements.toArray( new Comparable[ elements.size() ] ); 358 Type[] types = elementTypes.toArray( new Type[ elementTypes.size() ] ); 359 360 if( Util.containsNull( types ) ) 361 return new Fields( comparables ); 362 363 return new Fields( comparables, types ); 364 } 365 366 public static Fields copyComparators( Fields toFields, Fields... fromFields ) 367 { 368 for( Fields fromField : fromFields ) 369 { 370 for( Comparable field : fromField ) 371 { 372 Comparator comparator = fromField.getComparator( field ); 373 374 if( comparator != null ) 375 toFields.setComparator( field, comparator ); 376 } 377 } 378 379 return toFields; 380 } 381 382 /** 383 * Method offsetSelector is a factory that makes new instances of Fields the given size but offset by startPos. 384 * The result Fields instance can only be used as a selector. 385 * 386 * @param size of type int 387 * @param startPos of type int 388 * @return Fields 389 */ 390 public static Fields offsetSelector( int size, int startPos ) 391 { 392 Fields fields = new Fields(); 393 394 fields.isOrdered = false; 395 fields.fields = expand( size, startPos ); 396 397 return fields; 398 } 399 400 private static Comparable[] expand( int size, int startPos ) 401 { 402 if( size < 1 ) 403 throw new TupleException( "invalid size for fields: " + size ); 404 405 if( startPos < 0 ) 406 throw new TupleException( "invalid start position for fields: " + startPos ); 407 408 Comparable[] fields = new Comparable[ size ]; 409 410 for( int i = 0; i < fields.length; i++ ) 411 fields[ i ] = i + startPos; 412 413 return fields; 414 } 415 416 /** 417 * Method resolve returns a new selector expanded on the given field declarations 418 * 419 * @param selector of type Fields 420 * @param fields of type Fields 421 * @return Fields 422 */ 423 public static Fields resolve( Fields selector, Fields... fields ) 424 { 425 boolean hasUnknowns = false; 426 int size = 0; 427 428 for( Fields field : fields ) 429 { 430 if( field.isUnknown() ) 431 hasUnknowns = true; 432 433 if( !field.isDefined() && field.isUnOrdered() ) 434 throw new TupleException( "unable to select from field set: " + field.printVerbose() ); 435 436 size += field.size(); 437 } 438 439 if( selector.isAll() ) 440 { 441 Fields result = fields[ 0 ]; 442 443 for( int i = 1; i < fields.length; i++ ) 444 result = result.append( fields[ i ] ); 445 446 return result; 447 } 448 449 if( selector.isReplace() ) 450 { 451 if( fields[ 1 ].isUnknown() ) 452 throw new TupleException( "cannot replace fields with unknown field declaration" ); 453 454 if( !fields[ 0 ].contains( fields[ 1 ] ) ) 455 throw new TupleException( "could not find all fields to be replaced, available: " + fields[ 0 ].printVerbose() + ", declared: " + fields[ 1 ].printVerbose() ); 456 457 Type[] types = fields[ 0 ].getTypes(); 458 459 if( types != null ) 460 { 461 for( int i = 1; i < fields.length; i++ ) 462 { 463 Type[] fieldTypes = fields[ i ].getTypes(); 464 if( fieldTypes == null ) 465 continue; 466 467 for( int j = 0; j < fieldTypes.length; j++ ) 468 fields[ 0 ] = fields[ 0 ].applyType( fields[ i ].get( j ), fieldTypes[ j ] ); 469 } 470 } 471 472 return fields[ 0 ]; 473 } 474 475 // we can't deal with anything but ALL 476 if( !selector.isDefined() ) 477 throw new TupleException( "unable to use given selector: " + selector ); 478 479 Set<String> notFound = new LinkedHashSet<String>(); 480 Set<String> found = new HashSet<String>(); 481 Fields result = size( selector.size() ); 482 483 if( hasUnknowns ) 484 size = -1; 485 486 Type[] types = null; 487 488 if( size != -1 ) 489 types = new Type[ size ]; 490 491 int offset = 0; 492 for( Fields current : fields ) 493 { 494 if( current.isNone() ) 495 continue; 496 497 resolveInto( notFound, found, selector, current, result, types, offset, size ); 498 offset += current.size(); 499 } 500 501 if( types != null && !Util.containsNull( types ) ) // don't apply types if any are null 502 result = result.applyTypes( types ); 503 504 notFound.removeAll( found ); 505 506 if( !notFound.isEmpty() ) 507 throw new FieldsResolverException( new Fields( join( size, fields ) ), new Fields( notFound.toArray( new Comparable[ notFound.size() ] ) ) ); 508 509 if( hasUnknowns ) 510 return selector; 511 512 return result; 513 } 514 515 private static void resolveInto( Set<String> notFound, Set<String> found, Fields selector, Fields current, Fields result, Type[] types, int offset, int size ) 516 { 517 for( int i = 0; i < selector.size(); i++ ) 518 { 519 Comparable field = selector.get( i ); 520 521 if( field instanceof String ) 522 { 523 int index = current.indexOfSafe( field ); 524 525 if( index == -1 ) 526 notFound.add( (String) field ); 527 else 528 result.set( i, handleFound( found, field ) ); 529 530 if( index != -1 && types != null && current.getType( index ) != null ) 531 types[ i ] = current.getType( index ); 532 533 continue; 534 } 535 536 int pos = current.translatePos( (Integer) field, size ) - offset; 537 538 if( pos >= current.size() || pos < 0 ) 539 continue; 540 541 Comparable thisField = current.get( pos ); 542 543 if( types != null && current.getType( pos ) != null ) 544 types[ i ] = current.getType( pos ); 545 546 if( thisField instanceof String ) 547 result.set( i, handleFound( found, thisField ) ); 548 else 549 result.set( i, field ); 550 } 551 } 552 553 private static Comparable handleFound( Set<String> found, Comparable field ) 554 { 555 if( found.contains( field ) ) 556 throw new TupleException( "field name already exists: " + field ); 557 558 found.add( (String) field ); 559 560 return field; 561 } 562 563 /** 564 * Method asDeclaration returns a new Fields instance for use as a declarator based on the given fields value. 565 * <p/> 566 * Typically this is used to convert a selector to a declarator. Simply, all numeric position fields are replaced 567 * by their absolute position. 568 * <p/> 569 * Comparators are preserved in the result. 570 * 571 * @param fields of type Fields 572 * @return Fields 573 */ 574 public static Fields asDeclaration( Fields fields ) 575 { 576 if( fields == null ) 577 return null; 578 579 if( fields.isNone() ) 580 return fields; 581 582 if( !fields.isDefined() ) 583 return UNKNOWN; 584 585 if( fields.isOrdered() ) 586 return fields; 587 588 Fields result = size( fields.size() ); 589 590 copy( null, result, fields, 0 ); 591 592 result.types = copyTypes( fields.types, result.size() ); 593 result.comparators = fields.comparators; 594 595 return result; 596 } 597 598 private static Fields asSelector( Fields fields ) 599 { 600 if( !fields.isDefined() ) 601 return UNKNOWN; 602 603 return fields; 604 } 605 606 private Fields() 607 { 608 } 609 610 /** 611 * Constructor Fields creates a new Fields instance. 612 * 613 * @param kind of type Kind 614 */ 615 @SuppressWarnings({"SameParameterValue"}) 616 protected Fields( Kind kind ) 617 { 618 this.kind = kind; 619 } 620 621 /** 622 * Constructor Fields creates a new Fields instance. 623 * 624 * @param fields of type Comparable... 625 */ 626 @ConstructorProperties({"fields"}) 627 public Fields( Comparable... fields ) 628 { 629 if( fields.length == 0 ) 630 this.kind = Kind.NONE; 631 else 632 this.fields = validate( fields ); 633 } 634 635 public Fields( Comparable field, Type type ) 636 { 637 this( names( field ), types( type ) ); 638 } 639 640 public Fields( Comparable[] fields, Type[] types ) 641 { 642 this( fields ); 643 644 if( isDefined() && types != null ) 645 { 646 if( this.fields.length != types.length ) 647 throw new IllegalArgumentException( "given types array must be same length as fields" ); 648 649 this.types = copyTypes( types, this.fields.length ); 650 } 651 } 652 653 /** 654 * Method isUnOrdered returns true if this instance is unordered. That is, it has relative numeric field positions. 655 * For example; [1,"a",2,-1] 656 * 657 * @return the unOrdered (type boolean) of this Fields object. 658 */ 659 public boolean isUnOrdered() 660 { 661 return !isOrdered || kind == Kind.ALL; 662 } 663 664 /** 665 * Method isOrdered returns true if this instance is ordered. That is, all numeric field positions are absolute. 666 * For example; [0,"a",2,3] 667 * 668 * @return the ordered (type boolean) of this Fields object. 669 */ 670 public boolean isOrdered() 671 { 672 return isOrdered || kind == Kind.UNKNOWN; 673 } 674 675 /** 676 * Method isDefined returns true if this instance is not a field set like {@link #ALL} or {@link #UNKNOWN}. 677 * 678 * @return the defined (type boolean) of this Fields object. 679 */ 680 public boolean isDefined() 681 { 682 return kind == null; 683 } 684 685 /** 686 * Method isOutSelector returns true if this instance is 'defined', or the field set {@link #ALL} or {@link #RESULTS}. 687 * 688 * @return the outSelector (type boolean) of this Fields object. 689 */ 690 public boolean isOutSelector() 691 { 692 return isAll() || isResults() || isReplace() || isSwap() || isDefined(); 693 } 694 695 /** 696 * Method isArgSelector returns true if this instance is 'defined' or the field set {@link #ALL}, {@link #GROUP}, or 697 * {@link #VALUES}. 698 * 699 * @return the argSelector (type boolean) of this Fields object. 700 */ 701 public boolean isArgSelector() 702 { 703 return isAll() || isNone() || isGroup() || isValues() || isDefined(); 704 } 705 706 /** 707 * Method isDeclarator returns true if this can be used as a declarator. Specifically if it is 'defined' or 708 * {@link #UNKNOWN}, {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}. 709 * 710 * @return the declarator (type boolean) of this Fields object. 711 */ 712 public boolean isDeclarator() 713 { 714 return isUnknown() || isNone() || isAll() || isArguments() || isGroup() || isValues() || isDefined(); 715 } 716 717 /** 718 * Method isNone returns returns true if this instance is the {@link #NONE} field set. 719 * 720 * @return the none (type boolean) of this Fields object. 721 */ 722 public boolean isNone() 723 { 724 return kind == Kind.NONE; 725 } 726 727 /** 728 * Method isAll returns true if this instance is the {@link #ALL} field set. 729 * 730 * @return the all (type boolean) of this Fields object. 731 */ 732 public boolean isAll() 733 { 734 return kind == Kind.ALL; 735 } 736 737 /** 738 * Method isUnknown returns true if this instance is the {@link #UNKNOWN} field set. 739 * 740 * @return the unknown (type boolean) of this Fields object. 741 */ 742 public boolean isUnknown() 743 { 744 return kind == Kind.UNKNOWN; 745 } 746 747 /** 748 * Method isArguments returns true if this instance is the {@link #ARGS} field set. 749 * 750 * @return the arguments (type boolean) of this Fields object. 751 */ 752 public boolean isArguments() 753 { 754 return kind == Kind.ARGS; 755 } 756 757 /** 758 * Method isValues returns true if this instance is the {@link #VALUES} field set. 759 * 760 * @return the values (type boolean) of this Fields object. 761 */ 762 public boolean isValues() 763 { 764 return kind == Kind.VALUES; 765 } 766 767 /** 768 * Method isResults returns true if this instance is the {@link #RESULTS} field set. 769 * 770 * @return the results (type boolean) of this Fields object. 771 */ 772 public boolean isResults() 773 { 774 return kind == Kind.RESULTS; 775 } 776 777 /** 778 * Method isReplace returns true if this instance is the {@link #REPLACE} field set. 779 * 780 * @return the replace (type boolean) of this Fields object. 781 */ 782 public boolean isReplace() 783 { 784 return kind == Kind.REPLACE; 785 } 786 787 /** 788 * Method isSwap returns true if this instance is the {@link #SWAP} field set. 789 * 790 * @return the swap (type boolean) of this Fields object. 791 */ 792 public boolean isSwap() 793 { 794 return kind == Kind.SWAP; 795 } 796 797 /** 798 * Method isKeys returns true if this instance is the {@link #GROUP} field set. 799 * 800 * @return the keys (type boolean) of this Fields object. 801 */ 802 public boolean isGroup() 803 { 804 return kind == Kind.GROUP; 805 } 806 807 /** 808 * Method isSubstitution returns true if this instance is a substitution fields set. Specifically if it is the field 809 * set {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}. 810 * 811 * @return the substitution (type boolean) of this Fields object. 812 */ 813 public boolean isSubstitution() 814 { 815 return isAll() || isArguments() || isGroup() || isValues(); 816 } 817 818 private Comparable[] validate( Comparable[] fields ) 819 { 820 isOrdered = true; 821 822 Set<Comparable> names = new HashSet<Comparable>(); 823 824 for( int i = 0; i < fields.length; i++ ) 825 { 826 Comparable field = fields[ i ]; 827 828 if( !( field instanceof String || field instanceof Integer ) ) 829 throw new IllegalArgumentException( String.format( "invalid field type (%s); must be String or Integer: ", field ) ); 830 831 if( names.contains( field ) ) 832 throw new IllegalArgumentException( "duplicate field name found: " + field ); 833 834 names.add( field ); 835 836 if( field instanceof Number && (Integer) field != i ) 837 isOrdered = false; 838 } 839 840 return fields; 841 } 842 843 final Comparable[] get() 844 { 845 return fields; 846 } 847 848 /** 849 * Method get returns the field name or position at the given index i. 850 * 851 * @param i is of type int 852 * @return Comparable 853 */ 854 public final Comparable get( int i ) 855 { 856 return fields[ i ]; 857 } 858 859 final void set( int i, Comparable comparable ) 860 { 861 fields[ i ] = comparable; 862 863 if( isOrdered() && comparable instanceof Integer ) 864 isOrdered = i == (Integer) comparable; 865 } 866 867 /** 868 * Method getPos returns the pos array of this Fields object. 869 * 870 * @return the pos (type int[]) of this Fields object. 871 */ 872 public int[] getPos() 873 { 874 if( thisPos != null ) 875 return thisPos; // do not clone 876 877 if( isAll() || isUnknown() ) 878 thisPos = EMPTY_INT; 879 else 880 thisPos = makeThisPos(); 881 882 return thisPos; 883 } 884 885 private int[] makeThisPos() 886 { 887 int[] pos = new int[ size() ]; 888 889 for( int i = 0; i < size(); i++ ) 890 { 891 Comparable field = get( i ); 892 893 if( field instanceof Number ) 894 pos[ i ] = (Integer) field; 895 else 896 pos[ i ] = i; 897 } 898 899 return pos; 900 } 901 902 private final Map<Fields, int[]> getPosCache() 903 { 904 if( posCache == null ) 905 posCache = new HashMap<Fields, int[]>(); 906 907 return posCache; 908 } 909 910 private final int[] putReturn( Fields fields, int[] pos ) 911 { 912 getPosCache().put( fields, pos ); 913 914 return pos; 915 } 916 917 public final int[] getPos( Fields fields ) 918 { 919 return getPos( fields, -1 ); 920 } 921 922 final int[] getPos( Fields fields, int tupleSize ) 923 { 924 // test for key, as we stuff a null value 925 if( !isUnknown() && getPosCache().containsKey( fields ) ) 926 return getPosCache().get( fields ); 927 928 if( fields.isAll() ) 929 return putReturn( fields, null ); // return null, not getPos() 930 931 if( isAll() ) 932 return putReturn( fields, fields.getPos() ); 933 934 // don't cache unknown 935 if( size() == 0 && isUnknown() ) 936 return translatePos( fields, tupleSize ); 937 938 int[] pos = translatePos( fields, size() ); 939 940 return putReturn( fields, pos ); 941 } 942 943 private int[] translatePos( Fields fields, int fieldSize ) 944 { 945 int[] pos = new int[ fields.size() ]; 946 947 for( int i = 0; i < fields.size(); i++ ) 948 { 949 Comparable field = fields.get( i ); 950 951 if( field instanceof Number ) 952 pos[ i ] = translatePos( (Integer) field, fieldSize ); 953 else 954 pos[ i ] = indexOf( field ); 955 } 956 957 return pos; 958 } 959 960 final int translatePos( Integer integer ) 961 { 962 return translatePos( integer, size() ); 963 } 964 965 final int translatePos( Integer integer, int size ) 966 { 967 if( size == -1 ) 968 return integer; 969 970 if( integer < 0 ) 971 integer = size + integer; 972 973 if( !isUnknown() && ( integer >= size || integer < 0 ) ) 974 throw new TupleException( "position value is too large: " + integer + ", positions in field: " + size ); 975 976 return integer; 977 } 978 979 /** 980 * Method getPos returns the index of the give field value in this Fields instance. The index corresponds to the 981 * Tuple value index in an associated Tuple instance. 982 * 983 * @param fieldName of type Comparable 984 * @return int 985 */ 986 public int getPos( Comparable fieldName ) 987 { 988 if( fieldName instanceof Number ) 989 return translatePos( (Integer) fieldName ); 990 else 991 return indexOf( fieldName ); 992 } 993 994 private final Map<Comparable, Integer> getIndex() 995 { 996 if( index != null ) 997 return index; 998 999 // make thread-safe by not having invalid intermediate state 1000 Map<Comparable, Integer> local = new HashMap<Comparable, Integer>(); 1001 1002 for( int i = 0; i < size(); i++ ) 1003 local.put( get( i ), i ); 1004 1005 return index = local; 1006 } 1007 1008 private int indexOf( Comparable fieldName ) 1009 { 1010 Integer result = getIndex().get( fieldName ); 1011 1012 if( result == null ) 1013 throw new FieldsResolverException( this, new Fields( fieldName ) ); 1014 1015 return result; 1016 } 1017 1018 int indexOfSafe( Comparable fieldName ) 1019 { 1020 Integer result = getIndex().get( fieldName ); 1021 1022 if( result == null ) 1023 return -1; 1024 1025 return result; 1026 } 1027 1028 /** 1029 * Method iterator return an unmodifiable iterator of field values. if {@link #isSubstitution()} returns true, 1030 * this iterator will be empty. 1031 * 1032 * @return Iterator 1033 */ 1034 public Iterator iterator() 1035 { 1036 return Collections.unmodifiableList( Arrays.asList( fields ) ).iterator(); 1037 } 1038 1039 /** 1040 * Method select returns a new Fields instance with fields specified by the given selector. 1041 * 1042 * @param selector of type Fields 1043 * @return Fields 1044 */ 1045 public Fields select( Fields selector ) 1046 { 1047 if( !isOrdered() ) 1048 throw new TupleException( "this fields instance can only be used as a selector" ); 1049 1050 if( selector.isAll() ) 1051 return this; 1052 1053 // supports -1_UNKNOWN_RETURNED 1054 // guarantees pos arguments remain selector positions, not absolute positions 1055 if( isUnknown() ) 1056 return asSelector( selector ); 1057 1058 if( selector.isNone() ) 1059 return NONE; 1060 1061 Fields result = size( selector.size() ); 1062 1063 for( int i = 0; i < selector.size(); i++ ) 1064 { 1065 Comparable field = selector.get( i ); 1066 1067 if( field instanceof String ) 1068 { 1069 result.set( i, get( indexOf( field ) ) ); 1070 continue; 1071 } 1072 1073 int pos = translatePos( (Integer) field ); 1074 1075 if( this.get( pos ) instanceof String ) 1076 result.set( i, this.get( pos ) ); 1077 else 1078 result.set( i, pos ); // use absolute position if no field name 1079 } 1080 1081 if( this.types != null ) 1082 { 1083 result.types = new Type[ result.size() ]; 1084 1085 for( int i = 0; i < selector.size(); i++ ) 1086 { 1087 Comparable field = selector.get( i ); 1088 1089 if( field instanceof String ) 1090 result.setType( i, getType( indexOf( field ) ) ); 1091 else 1092 result.setType( i, getType( translatePos( (Integer) field ) ) ); 1093 } 1094 } 1095 1096 return result; 1097 } 1098 1099 /** 1100 * Method selectPos returns a Fields instance with only positional fields, no field names. 1101 * 1102 * @param selector of type Fields 1103 * @return Fields instance with only positions. 1104 */ 1105 public Fields selectPos( Fields selector ) 1106 { 1107 return selectPos( selector, 0 ); 1108 } 1109 1110 /** 1111 * Method selectPos returns a Fields instance with only positional fields, offset by given offset value, no field names. 1112 * 1113 * @param selector of type Fields 1114 * @param offset of type int 1115 * @return Fields instance with only positions. 1116 */ 1117 public Fields selectPos( Fields selector, int offset ) 1118 { 1119 int[] pos = getPos( selector ); 1120 1121 Fields results = size( pos.length ); 1122 1123 for( int i = 0; i < pos.length; i++ ) 1124 results.fields[ i ] = pos[ i ] + offset; 1125 1126 return results; 1127 } 1128 1129 /** 1130 * Method subtract returns the difference between this instance and the given fields instance. 1131 * <p/> 1132 * See {@link #append(Fields)} for adding field names. 1133 * 1134 * @param fields of type Fields 1135 * @return Fields 1136 */ 1137 public Fields subtract( Fields fields ) 1138 { 1139 if( fields.isAll() ) 1140 return Fields.NONE; 1141 1142 if( fields.isNone() ) 1143 return this; 1144 1145 List<Comparable> list = new LinkedList<Comparable>(); 1146 Collections.addAll( list, this.get() ); 1147 int[] pos = getPos( fields, -1 ); 1148 1149 for( int i : pos ) 1150 list.set( i, null ); 1151 1152 Util.removeAllNulls( list ); 1153 1154 Type[] newTypes = null; 1155 1156 if( this.types != null ) 1157 { 1158 List<Type> types = new LinkedList<Type>(); 1159 Collections.addAll( types, this.types ); 1160 1161 for( int i : pos ) 1162 types.set( i, null ); 1163 1164 Util.removeAllNulls( types ); 1165 1166 newTypes = types.toArray( new Type[ types.size() ] ); 1167 } 1168 1169 return new Fields( list.toArray( new Comparable[ list.size() ] ), newTypes ); 1170 } 1171 1172 /** 1173 * Method is used for appending the given Fields instance to this instance, into a new Fields instance. 1174 * <p/> 1175 * See {@link #subtract(Fields)} for removing field names. 1176 * <p/> 1177 * This method has been deprecated, see {@link #join(Fields...)} 1178 * 1179 * @param fields of type Fields[] 1180 * @return Fields 1181 */ 1182 @Deprecated 1183 public Fields append( Fields[] fields ) 1184 { 1185 if( fields.length == 0 ) 1186 return null; 1187 1188 Fields field = this; 1189 1190 for( Fields current : fields ) 1191 field = field.append( current ); 1192 1193 return field; 1194 } 1195 1196 /** 1197 * Method is used for appending the given Fields instance to this instance, into a new Fields instance suitable 1198 * for use as a field declaration. 1199 * <p/> 1200 * That is, any positional elements (including relative positions like {@code -1}, will be ignored during the 1201 * append. For example, the second {@code 0} position is lost in the result. 1202 * <p/> 1203 * {@code assert new Fields( 0, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 0, "a", 2, "b" )} 1204 * <p/> 1205 * See {@link #subtract(Fields)} for removing field names. 1206 * 1207 * @param fields of type Fields 1208 * @return Fields 1209 */ 1210 public Fields append( Fields fields ) 1211 { 1212 return appendInternal( fields, false ); 1213 } 1214 1215 /** 1216 * Method is used for appending the given Fields instance to this instance, into a new Fields instance 1217 * suitable for use as a field selector. 1218 * <p/> 1219 * That is, any positional elements will be retained during the append. For example, the {@code 5} and {@code 0} 1220 * are retained in the result. 1221 * <p/> 1222 * {@code assert new Fields( 5, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 5, "a", 0, "b" )} 1223 * <p/> 1224 * Note any relative positional elements are retained, thus appending two Fields each declaring {@code -1} 1225 * position will result in a TupleException noting duplicate fields. 1226 * <p/> 1227 * See {@link #subtract(Fields)} for removing field names. 1228 * 1229 * @param fields of type Fields 1230 * @return Fields 1231 */ 1232 public Fields appendSelector( Fields fields ) 1233 { 1234 return appendInternal( fields, true ); 1235 } 1236 1237 private Fields appendInternal( Fields fields, boolean isSelect ) 1238 { 1239 if( fields == null ) 1240 return this; 1241 1242 // allow unordered fields to be appended to build more complex selectors 1243 if( this.isAll() || fields.isAll() ) 1244 throw new TupleException( "cannot append fields: " + this.print() + " + " + fields.print() ); 1245 1246 if( ( this.isUnknown() || this.size() == 0 ) && fields.isUnknown() ) 1247 return UNKNOWN; 1248 1249 if( fields.isNone() ) 1250 return this; 1251 1252 if( this.isNone() ) 1253 return fields; 1254 1255 Set<Comparable> names = new HashSet<Comparable>(); 1256 1257 // init the Field 1258 Fields result = size( this.size() + fields.size() ); 1259 1260 // copy over field names from this side 1261 copyRetain( names, result, this, 0, isSelect ); 1262 // copy over field names from that side 1263 copyRetain( names, result, fields, this.size(), isSelect ); 1264 1265 if( this.isUnknown() || fields.isUnknown() ) 1266 result.kind = Kind.UNKNOWN; 1267 1268 if( ( this.isNone() || this.types != null ) && fields.types != null ) 1269 { 1270 result.types = new Type[ this.size() + fields.size() ]; 1271 1272 if( this.types != null ) // supports appending to NONE 1273 System.arraycopy( this.types, 0, result.types, 0, this.size() ); 1274 1275 System.arraycopy( fields.types, 0, result.types, this.size(), fields.size() ); 1276 } 1277 1278 return result; 1279 } 1280 1281 /** 1282 * Method rename will rename the from fields to the values in to to fields. Fields may contain field names, or 1283 * positions. 1284 * <p/> 1285 * Using positions is useful to remove a field name put keep its place in the Tuple stream. 1286 * 1287 * @param from of type Fields 1288 * @param to of type Fields 1289 * @return Fields 1290 */ 1291 public Fields rename( Fields from, Fields to ) 1292 { 1293 if( this.isSubstitution() || this.isUnknown() ) 1294 throw new TupleException( "cannot rename fields in a substitution or unknown Fields instance: " + this.print() ); 1295 1296 if( from.size() != to.size() ) 1297 throw new TupleException( "from and to fields must be the same size" ); 1298 1299 if( from.isSubstitution() || from.isUnknown() ) 1300 throw new TupleException( "from fields may not be a substitution or unknown" ); 1301 1302 if( to.isSubstitution() || to.isUnknown() ) 1303 throw new TupleException( "to fields may not be a substitution or unknown" ); 1304 1305 Comparable[] newFields = Arrays.copyOf( this.fields, this.fields.length ); 1306 1307 int[] pos = getPos( from ); 1308 1309 for( int i = 0; i < pos.length; i++ ) 1310 newFields[ pos[ i ] ] = to.fields[ i ]; 1311 1312 Type[] newTypes = null; 1313 1314 if( this.types != null && to.types != null ) 1315 { 1316 newTypes = copyTypes( this.types, this.size() ); 1317 1318 for( int i = 0; i < pos.length; i++ ) 1319 newTypes[ pos[ i ] ] = to.types[ i ]; 1320 } 1321 1322 return new Fields( newFields, newTypes ); 1323 } 1324 1325 /** 1326 * Method project will return a new Fields instance similar to the given fields instance 1327 * except any absolute positional elements will be replaced by the current field names, if any. 1328 * 1329 * @param fields of type Fields 1330 * @return Fields 1331 */ 1332 public Fields project( Fields fields ) 1333 { 1334 if( fields == null ) 1335 return this; 1336 1337 Fields results = size( fields.size() ).applyTypes( fields.getTypes() ); 1338 1339 for( int i = 0; i < fields.fields.length; i++ ) 1340 { 1341 if( fields.fields[ i ] instanceof String ) 1342 results.fields[ i ] = fields.fields[ i ]; 1343 else if( this.fields[ i ] instanceof String ) 1344 results.fields[ i ] = this.fields[ i ]; 1345 else 1346 results.fields[ i ] = i; 1347 } 1348 1349 return results; 1350 } 1351 1352 private static void copy( Set<String> names, Fields result, Fields fields, int offset ) 1353 { 1354 for( int i = 0; i < fields.size(); i++ ) 1355 { 1356 Comparable field = fields.get( i ); 1357 1358 if( !( field instanceof String ) ) 1359 continue; 1360 1361 if( names != null ) 1362 { 1363 if( names.contains( field ) ) 1364 throw new TupleException( "field name already exists: " + field ); 1365 1366 names.add( (String) field ); 1367 } 1368 1369 result.set( i + offset, field ); 1370 } 1371 } 1372 1373 /** 1374 * Retains any relative positional elements like -1, but checks for duplicates 1375 * 1376 * @param names 1377 * @param result 1378 * @param fields 1379 * @param offset 1380 * @param isSelect 1381 */ 1382 private static void copyRetain( Set<Comparable> names, Fields result, Fields fields, int offset, boolean isSelect ) 1383 { 1384 for( int i = 0; i < fields.size(); i++ ) 1385 { 1386 Comparable field = fields.get( i ); 1387 1388 if( !isSelect && field instanceof Integer ) 1389 continue; 1390 1391 if( names != null ) 1392 { 1393 if( names.contains( field ) ) 1394 throw new TupleException( "field name already exists: " + field ); 1395 1396 names.add( field ); 1397 } 1398 1399 result.set( i + offset, field ); 1400 } 1401 } 1402 1403 /** 1404 * Method verifyContains tests if this instance contains the field names and positions specified in the given 1405 * fields instance. If the test fails, a {@link TupleException} is thrown. 1406 * 1407 * @param fields of type Fields 1408 * @throws TupleException when one or more fields are not contained in this instance. 1409 */ 1410 public void verifyContains( Fields fields ) 1411 { 1412 if( isUnknown() ) 1413 return; 1414 1415 try 1416 { 1417 getPos( fields ); 1418 } 1419 catch( TupleException exception ) 1420 { 1421 throw new TupleException( "these fields " + print() + ", do not contain " + fields.print() ); 1422 } 1423 } 1424 1425 /** 1426 * Method contains returns true if this instance contains the field names and positions specified in the given 1427 * fields instance. 1428 * 1429 * @param fields of type Fields 1430 * @return boolean 1431 */ 1432 public boolean contains( Fields fields ) 1433 { 1434 try 1435 { 1436 getPos( fields ); 1437 return true; 1438 } 1439 catch( Exception exception ) 1440 { 1441 return false; 1442 } 1443 } 1444 1445 /** 1446 * Method compareTo compares this instance to the given Fields instance. 1447 * 1448 * @param other of type Fields 1449 * @return int 1450 */ 1451 public int compareTo( Fields other ) 1452 { 1453 if( other.size() != size() ) 1454 return other.size() < size() ? 1 : -1; 1455 1456 for( int i = 0; i < size(); i++ ) 1457 { 1458 int c = get( i ).compareTo( other.get( i ) ); 1459 1460 if( c != 0 ) 1461 return c; 1462 } 1463 1464 return 0; 1465 } 1466 1467 /** 1468 * Method compareTo implements {@link Comparable#compareTo(Object)}. 1469 * 1470 * @param other of type Object 1471 * @return int 1472 */ 1473 public int compareTo( Object other ) 1474 { 1475 if( other instanceof Fields ) 1476 return compareTo( (Fields) other ); 1477 else 1478 return -1; 1479 } 1480 1481 /** 1482 * Method print returns a String representation of this instance. 1483 * 1484 * @return String 1485 */ 1486 public String print() 1487 { 1488 return "[" + toString() + "]"; 1489 } 1490 1491 /** 1492 * Method printLong returns a String representation of this instance along with the size. 1493 * 1494 * @return String 1495 */ 1496 public String printVerbose() 1497 { 1498 String fieldsString = toString(); 1499 1500 return "[{" + ( isDefined() ? size() : "?" ) + "}:" + fieldsString + "]"; 1501 } 1502 1503 1504 @Override 1505 public String toString() 1506 { 1507 String string; 1508 1509 if( isOrdered() ) 1510 string = orderedToString(); 1511 else 1512 string = unorderedToString(); 1513 1514 if( types != null ) 1515 string += " | " + Util.join( Util.simpleTypeNames( types ), ", " ); 1516 1517 return string; 1518 } 1519 1520 private String orderedToString() 1521 { 1522 StringBuffer buffer = new StringBuffer(); 1523 1524 if( size() != 0 ) 1525 { 1526 int startIndex = get( 0 ) instanceof Number ? (Integer) get( 0 ) : 0; 1527 1528 for( int i = 0; i < size(); i++ ) 1529 { 1530 Comparable field = get( i ); 1531 1532 if( field instanceof Number ) 1533 { 1534 if( i + 1 == size() || !( get( i + 1 ) instanceof Number ) ) 1535 { 1536 if( buffer.length() != 0 ) 1537 buffer.append( ", " ); 1538 1539 if( startIndex != i ) 1540 buffer.append( startIndex ).append( ":" ).append( field ); 1541 else 1542 buffer.append( i ); 1543 1544 startIndex = i; 1545 } 1546 1547 continue; 1548 } 1549 1550 if( i != 0 ) 1551 buffer.append( ", " ); 1552 1553 if( field instanceof String ) 1554 buffer.append( "\'" ).append( field ).append( "\'" ); 1555 else if( field instanceof Fields ) 1556 buffer.append( ( (Fields) field ).print() ); 1557 1558 startIndex = i + 1; 1559 } 1560 } 1561 1562 if( kind != null ) 1563 { 1564 if( buffer.length() != 0 ) 1565 buffer.append( ", " ); 1566 buffer.append( kind ); 1567 } 1568 1569 return buffer.toString(); 1570 } 1571 1572 private String unorderedToString() 1573 { 1574 StringBuffer buffer = new StringBuffer(); 1575 1576 for( Object field : get() ) 1577 { 1578 if( buffer.length() != 0 ) 1579 buffer.append( ", " ); 1580 1581 if( field instanceof String ) 1582 buffer.append( "\'" ).append( field ).append( "\'" ); 1583 else if( field instanceof Fields ) 1584 buffer.append( ( (Fields) field ).print() ); 1585 else 1586 buffer.append( field ); 1587 } 1588 1589 if( kind != null ) 1590 { 1591 if( buffer.length() != 0 ) 1592 buffer.append( ", " ); 1593 buffer.append( kind ); 1594 } 1595 1596 return buffer.toString(); 1597 } 1598 1599 /** 1600 * Method size returns the number of field positions in this instance. 1601 * 1602 * @return int 1603 */ 1604 public final int size() 1605 { 1606 return fields.length; 1607 } 1608 1609 /** 1610 * Method applyType should be used to associate a {@link java.lang.reflect.Type} with a given field name or position. 1611 * A new instance of Fields will be returned, this instance will not be modified. 1612 * <p/> 1613 * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will 1614 * be considered. 1615 * 1616 * @param fieldName of type Comparable 1617 * @param type of type Type 1618 */ 1619 public Fields applyType( Comparable fieldName, Type type ) 1620 { 1621 if( type == null ) 1622 throw new IllegalArgumentException( "given type must not be null" ); 1623 1624 int pos; 1625 1626 try 1627 { 1628 pos = getPos( asFieldName( fieldName ) ); 1629 } 1630 catch( FieldsResolverException exception ) 1631 { 1632 throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception ); 1633 } 1634 1635 Fields results = new Fields( fields ); 1636 1637 results.types = this.types == null ? new Type[ size() ] : this.types; 1638 results.types[ pos ] = type; 1639 1640 return results; 1641 } 1642 1643 /** 1644 * Method applyType should be used to associate {@link java.lang.reflect.Type} with a given field name or position 1645 * as declared in the given Fields parameter. 1646 * <p/> 1647 * A new instance of Fields will be returned, this instance will not be modified. 1648 * <p/> 1649 * 1650 * @param fields of type Fields 1651 */ 1652 public Fields applyTypes( Fields fields ) 1653 { 1654 Fields result = new Fields( this.fields, this.types ); 1655 1656 for( Comparable field : fields ) 1657 result = result.applyType( field, fields.getType( fields.getPos( field ) ) ); 1658 1659 return result; 1660 } 1661 1662 /** 1663 * Method applyTypes returns a new Fields instance with the given types, replacing any existing type 1664 * information within the new instance. 1665 * <p/> 1666 * The Class array must be the same length as the number for fields in this instance. 1667 * 1668 * @param types the class types of this Fields object. 1669 * @return returns a new instance of Fields with this instances field names and the given types 1670 */ 1671 public Fields applyTypes( Type... types ) 1672 { 1673 Fields result = new Fields( fields ); 1674 1675 if( types == null ) // allows for type erasure 1676 return result; 1677 1678 if( types.length != size() ) 1679 throw new IllegalArgumentException( "given number of class instances must match fields size" ); 1680 1681 for( Type type : types ) 1682 { 1683 if( type == null ) 1684 throw new IllegalArgumentException( "type must not be null" ); 1685 } 1686 1687 result.types = copyTypes( types, types.length ); // make copy as Class[] could be passed in 1688 1689 return result; 1690 } 1691 1692 /** 1693 * Returns the Type at the given position or having the fieldName. 1694 * 1695 * @param fieldName of type String or Number 1696 * @return the Type 1697 */ 1698 public Type getType( Comparable fieldName ) 1699 { 1700 if( !hasTypes() ) 1701 return null; 1702 1703 return getType( getPos( fieldName ) ); 1704 } 1705 1706 public Type getType( int pos ) 1707 { 1708 if( !hasTypes() ) 1709 return null; 1710 1711 return this.types[ pos ]; 1712 } 1713 1714 /** 1715 * Returns the Class for the given position value. 1716 * <p/> 1717 * If the underlying value is of type {@link CoercibleType}, the result of 1718 * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned. 1719 * 1720 * @param fieldName of type String or Number 1721 * @return type Class 1722 */ 1723 public Class getTypeClass( Comparable fieldName ) 1724 { 1725 if( !hasTypes() ) 1726 return null; 1727 1728 return getTypeClass( getPos( fieldName ) ); 1729 } 1730 1731 public Class getTypeClass( int pos ) 1732 { 1733 Type type = getType( pos ); 1734 1735 if( type instanceof CoercibleType ) 1736 return ( (CoercibleType) type ).getCanonicalType(); 1737 1738 return (Class) type; 1739 } 1740 1741 protected void setType( int pos, Type type ) 1742 { 1743 if( type == null ) 1744 throw new IllegalArgumentException( "type may not be null" ); 1745 1746 this.types[ pos ] = type; 1747 } 1748 1749 /** 1750 * Returns a copy of the current types Type[] if any, else null. 1751 * 1752 * @return of type Type[] 1753 */ 1754 public Type[] getTypes() 1755 { 1756 return copyTypes( types, size() ); 1757 } 1758 1759 /** 1760 * Returns a copy of the current types Class[] if any, else null. 1761 * <p/> 1762 * If any underlying value is of type {@link CoercibleType}, the result of 1763 * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned. 1764 * 1765 * @return of type Class 1766 */ 1767 public Class[] getTypesClasses() 1768 { 1769 if( types == null ) 1770 return null; 1771 1772 Class[] classes = new Class[ types.length ]; 1773 1774 for( int i = 0; i < types.length; i++ ) 1775 { 1776 if( types[ i ] instanceof CoercibleType ) 1777 classes[ i ] = ( (CoercibleType) types[ i ] ).getCanonicalType(); 1778 else 1779 classes[ i ] = (Class) types[ i ]; // this throws a more helpful exception vs arraycopy 1780 } 1781 1782 return classes; 1783 } 1784 1785 private static Type[] copyTypes( Type[] types, int size ) 1786 { 1787 if( types == null ) 1788 return null; 1789 1790 Type[] copy = new Type[ size ]; 1791 1792 if( types.length != size ) 1793 throw new IllegalArgumentException( "types array must be same size as fields array" ); 1794 1795 System.arraycopy( types, 0, copy, 0, size ); 1796 1797 return copy; 1798 } 1799 1800 /** 1801 * Returns true if there are types associated with this instance. 1802 * 1803 * @return boolean 1804 */ 1805 public final boolean hasTypes() 1806 { 1807 return types != null; 1808 } 1809 1810 /** 1811 * Method setComparator should be used to associate a {@link java.util.Comparator} with a given field name or position. 1812 * <p/> 1813 * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will 1814 * be considered. 1815 * 1816 * @param fieldName of type Comparable 1817 * @param comparator of type Comparator 1818 */ 1819 public void setComparator( Comparable fieldName, Comparator comparator ) 1820 { 1821 if( !( comparator instanceof Serializable ) ) 1822 throw new IllegalArgumentException( "given comparator must be serializable" ); 1823 1824 if( comparators == null ) 1825 comparators = new Comparator[ size() ]; 1826 1827 try 1828 { 1829 comparators[ getPos( asFieldName( fieldName ) ) ] = comparator; 1830 } 1831 catch( FieldsResolverException exception ) 1832 { 1833 throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception ); 1834 } 1835 } 1836 1837 /** 1838 * Method setComparators sets all the comparators of this Fields object. The Comparator array 1839 * must be the same length as the number for fields in this instance. 1840 * 1841 * @param comparators the comparators of this Fields object. 1842 */ 1843 public void setComparators( Comparator... comparators ) 1844 { 1845 if( comparators.length != size() ) 1846 throw new IllegalArgumentException( "given number of comparator instances must match fields size" ); 1847 1848 for( Comparator comparator : comparators ) 1849 { 1850 if( !( comparator instanceof Serializable ) ) 1851 throw new IllegalArgumentException( "comparators must be serializable" ); 1852 } 1853 1854 this.comparators = comparators; 1855 } 1856 1857 protected static Comparable asFieldName( Comparable fieldName ) 1858 { 1859 if( fieldName instanceof Fields ) 1860 { 1861 Fields fields = (Fields) fieldName; 1862 1863 if( !fields.isDefined() ) 1864 throw new TupleException( "given Fields instance must explicitly declare one field name or position: " + fields.printVerbose() ); 1865 1866 fieldName = fields.get( 0 ); 1867 } 1868 1869 return fieldName; 1870 } 1871 1872 protected Comparator getComparator( Comparable fieldName ) 1873 { 1874 if( comparators == null ) 1875 return null; 1876 1877 try 1878 { 1879 return comparators[ getPos( asFieldName( fieldName ) ) ]; 1880 } 1881 catch( FieldsResolverException exception ) 1882 { 1883 return null; 1884 } 1885 } 1886 1887 /** 1888 * Method getComparators returns the comparators of this Fields object. 1889 * 1890 * @return the comparators (type Comparator[]) of this Fields object. 1891 */ 1892 public Comparator[] getComparators() 1893 { 1894 Comparator[] copy = new Comparator[ size() ]; 1895 1896 if( comparators != null ) 1897 System.arraycopy( comparators, 0, copy, 0, size() ); 1898 1899 return copy; 1900 } 1901 1902 /** 1903 * Method hasComparators test if this Fields instance has Comparators. 1904 * 1905 * @return boolean 1906 */ 1907 public boolean hasComparators() 1908 { 1909 return comparators != null; 1910 } 1911 1912 @Override 1913 public int compare( Tuple lhs, Tuple rhs ) 1914 { 1915 return lhs.compareTo( comparators, rhs ); 1916 } 1917 1918 @Override 1919 public boolean equals( Object object ) 1920 { 1921 if( this == object ) 1922 return true; 1923 if( object == null || getClass() != object.getClass() ) 1924 return false; 1925 1926 Fields fields = (Fields) object; 1927 1928 return equalsFields( fields ) && Arrays.equals( types, fields.types ); 1929 } 1930 1931 /** 1932 * Method equalsFields compares only the internal field names and postions only between this and the given Fields 1933 * instance. Type information is ignored. 1934 * 1935 * @param fields of type int 1936 * @return true if this and the given instance have the same positions and/or field names. 1937 */ 1938 public boolean equalsFields( Fields fields ) 1939 { 1940 return fields != null && this.kind == fields.kind && Arrays.equals( get(), fields.get() ); 1941 } 1942 1943 @Override 1944 public int hashCode() 1945 { 1946 if( hashCode == 0 ) 1947 hashCode = get() != null ? Arrays.hashCode( get() ) : 0; 1948 1949 return hashCode; 1950 } 1951 }