001/* 002 * Copyright (c) 2007-2015 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 021package cascading.tuple; 022 023import java.beans.ConstructorProperties; 024import java.io.Serializable; 025import java.lang.reflect.Type; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collections; 029import java.util.Comparator; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.Iterator; 033import java.util.LinkedHashSet; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.Map; 037import java.util.Set; 038 039import cascading.tap.Tap; 040import cascading.tuple.type.CoercibleType; 041import 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 */ 096public 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 465 if( fieldTypes == null ) 466 { 467 fields[ 0 ] = fields[ 0 ].applyTypes( (Type[]) null ); 468 } 469 else 470 { 471 for( int j = 0; j < fieldTypes.length; j++ ) 472 fields[ 0 ] = fields[ 0 ].applyType( fields[ i ].get( j ), fieldTypes[ j ] ); 473 } 474 } 475 } 476 477 return fields[ 0 ]; 478 } 479 480 // we can't deal with anything but ALL 481 if( !selector.isDefined() ) 482 throw new TupleException( "unable to use given selector: " + selector ); 483 484 Set<String> notFound = new LinkedHashSet<String>(); 485 Set<String> found = new HashSet<String>(); 486 Fields result = size( selector.size() ); 487 488 if( hasUnknowns ) 489 size = -1; 490 491 Type[] types = null; 492 493 if( size != -1 ) 494 types = new Type[ result.size() ]; 495 496 int offset = 0; 497 for( Fields current : fields ) 498 { 499 if( current.isNone() ) 500 continue; 501 502 resolveInto( notFound, found, selector, current, result, types, offset, size ); 503 offset += current.size(); 504 } 505 506 if( types != null && !Util.containsNull( types ) ) // don't apply types if any are null 507 result = result.applyTypes( types ); 508 509 notFound.removeAll( found ); 510 511 if( !notFound.isEmpty() ) 512 throw new FieldsResolverException( new Fields( join( size, fields ) ), new Fields( notFound.toArray( new Comparable[ notFound.size() ] ) ) ); 513 514 if( hasUnknowns ) 515 return selector; 516 517 return result; 518 } 519 520 private static void resolveInto( Set<String> notFound, Set<String> found, Fields selector, Fields current, Fields result, Type[] types, int offset, int size ) 521 { 522 for( int i = 0; i < selector.size(); i++ ) 523 { 524 Comparable field = selector.get( i ); 525 526 if( field instanceof String ) 527 { 528 int index = current.indexOfSafe( field ); 529 530 if( index == -1 ) 531 notFound.add( (String) field ); 532 else 533 result.set( i, handleFound( found, field ) ); 534 535 if( index != -1 && types != null && current.getType( index ) != null ) 536 types[ i ] = current.getType( index ); 537 538 continue; 539 } 540 541 int pos = current.translatePos( (Integer) field, size ) - offset; 542 543 if( pos >= current.size() || pos < 0 ) 544 continue; 545 546 Comparable thisField = current.get( pos ); 547 548 if( types != null && current.getType( pos ) != null ) 549 types[ i ] = current.getType( pos ); 550 551 if( thisField instanceof String ) 552 result.set( i, handleFound( found, thisField ) ); 553 else 554 result.set( i, field ); 555 } 556 } 557 558 private static Comparable handleFound( Set<String> found, Comparable field ) 559 { 560 if( found.contains( field ) ) 561 throw new TupleException( "field name already exists: " + field ); 562 563 found.add( (String) field ); 564 565 return field; 566 } 567 568 /** 569 * Method asDeclaration returns a new Fields instance for use as a declarator based on the given fields value. 570 * <p/> 571 * Typically this is used to convert a selector to a declarator. Simply, all numeric position fields are replaced 572 * by their absolute position. 573 * <p/> 574 * Comparators are preserved in the result. 575 * 576 * @param fields of type Fields 577 * @return Fields 578 */ 579 public static Fields asDeclaration( Fields fields ) 580 { 581 if( fields == null ) 582 return null; 583 584 if( fields.isNone() ) 585 return fields; 586 587 if( !fields.isDefined() ) 588 return UNKNOWN; 589 590 if( fields.isOrdered() ) 591 return fields; 592 593 Fields result = size( fields.size() ); 594 595 copy( null, result, fields, 0 ); 596 597 result.types = copyTypes( fields.types, result.size() ); 598 result.comparators = fields.comparators; 599 600 return result; 601 } 602 603 private static Fields asSelector( Fields fields ) 604 { 605 if( !fields.isDefined() ) 606 return UNKNOWN; 607 608 return fields; 609 } 610 611 private Fields() 612 { 613 } 614 615 /** 616 * Constructor Fields creates a new Fields instance. 617 * 618 * @param kind of type Kind 619 */ 620 @SuppressWarnings({"SameParameterValue"}) 621 protected Fields( Kind kind ) 622 { 623 this.kind = kind; 624 } 625 626 /** 627 * Constructor Fields creates a new Fields instance. 628 * 629 * @param fields of type Comparable... 630 */ 631 @ConstructorProperties({"fields"}) 632 public Fields( Comparable... fields ) 633 { 634 if( fields.length == 0 ) 635 this.kind = Kind.NONE; 636 else 637 this.fields = validate( fields ); 638 } 639 640 public Fields( Comparable field, Type type ) 641 { 642 this( names( field ), types( type ) ); 643 } 644 645 public Fields( Comparable[] fields, Type[] types ) 646 { 647 this( fields ); 648 649 if( isDefined() && types != null ) 650 { 651 if( this.fields.length != types.length ) 652 throw new IllegalArgumentException( "given types array must be same length as fields" ); 653 654 if( Util.containsNull( types ) ) 655 throw new IllegalArgumentException( "given types array contains null" ); 656 657 this.types = copyTypes( types, this.fields.length ); 658 } 659 } 660 661 /** 662 * Method isUnOrdered returns true if this instance is unordered. That is, it has relative numeric field positions. 663 * For example; [1,"a",2,-1] 664 * 665 * @return the unOrdered (type boolean) of this Fields object. 666 */ 667 public boolean isUnOrdered() 668 { 669 return !isOrdered || kind == Kind.ALL; 670 } 671 672 /** 673 * Method isOrdered returns true if this instance is ordered. That is, all numeric field positions are absolute. 674 * For example; [0,"a",2,3] 675 * 676 * @return the ordered (type boolean) of this Fields object. 677 */ 678 public boolean isOrdered() 679 { 680 return isOrdered || kind == Kind.UNKNOWN; 681 } 682 683 /** 684 * Method isDefined returns true if this instance is not a field set like {@link #ALL} or {@link #UNKNOWN}. 685 * 686 * @return the defined (type boolean) of this Fields object. 687 */ 688 public boolean isDefined() 689 { 690 return kind == null; 691 } 692 693 /** 694 * Method isOutSelector returns true if this instance is 'defined', or the field set {@link #ALL} or {@link #RESULTS}. 695 * 696 * @return the outSelector (type boolean) of this Fields object. 697 */ 698 public boolean isOutSelector() 699 { 700 return isAll() || isResults() || isReplace() || isSwap() || isDefined(); 701 } 702 703 /** 704 * Method isArgSelector returns true if this instance is 'defined' or the field set {@link #ALL}, {@link #GROUP}, or 705 * {@link #VALUES}. 706 * 707 * @return the argSelector (type boolean) of this Fields object. 708 */ 709 public boolean isArgSelector() 710 { 711 return isAll() || isNone() || isGroup() || isValues() || isDefined(); 712 } 713 714 /** 715 * Method isDeclarator returns true if this can be used as a declarator. Specifically if it is 'defined' or 716 * {@link #UNKNOWN}, {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}. 717 * 718 * @return the declarator (type boolean) of this Fields object. 719 */ 720 public boolean isDeclarator() 721 { 722 return isUnknown() || isNone() || isAll() || isArguments() || isGroup() || isValues() || isDefined(); 723 } 724 725 /** 726 * Method isNone returns returns true if this instance is the {@link #NONE} field set. 727 * 728 * @return the none (type boolean) of this Fields object. 729 */ 730 public boolean isNone() 731 { 732 return kind == Kind.NONE; 733 } 734 735 /** 736 * Method isAll returns true if this instance is the {@link #ALL} field set. 737 * 738 * @return the all (type boolean) of this Fields object. 739 */ 740 public boolean isAll() 741 { 742 return kind == Kind.ALL; 743 } 744 745 /** 746 * Method isUnknown returns true if this instance is the {@link #UNKNOWN} field set. 747 * 748 * @return the unknown (type boolean) of this Fields object. 749 */ 750 public boolean isUnknown() 751 { 752 return kind == Kind.UNKNOWN; 753 } 754 755 /** 756 * Method isArguments returns true if this instance is the {@link #ARGS} field set. 757 * 758 * @return the arguments (type boolean) of this Fields object. 759 */ 760 public boolean isArguments() 761 { 762 return kind == Kind.ARGS; 763 } 764 765 /** 766 * Method isValues returns true if this instance is the {@link #VALUES} field set. 767 * 768 * @return the values (type boolean) of this Fields object. 769 */ 770 public boolean isValues() 771 { 772 return kind == Kind.VALUES; 773 } 774 775 /** 776 * Method isResults returns true if this instance is the {@link #RESULTS} field set. 777 * 778 * @return the results (type boolean) of this Fields object. 779 */ 780 public boolean isResults() 781 { 782 return kind == Kind.RESULTS; 783 } 784 785 /** 786 * Method isReplace returns true if this instance is the {@link #REPLACE} field set. 787 * 788 * @return the replace (type boolean) of this Fields object. 789 */ 790 public boolean isReplace() 791 { 792 return kind == Kind.REPLACE; 793 } 794 795 /** 796 * Method isSwap returns true if this instance is the {@link #SWAP} field set. 797 * 798 * @return the swap (type boolean) of this Fields object. 799 */ 800 public boolean isSwap() 801 { 802 return kind == Kind.SWAP; 803 } 804 805 /** 806 * Method isKeys returns true if this instance is the {@link #GROUP} field set. 807 * 808 * @return the keys (type boolean) of this Fields object. 809 */ 810 public boolean isGroup() 811 { 812 return kind == Kind.GROUP; 813 } 814 815 /** 816 * Method isSubstitution returns true if this instance is a substitution fields set. Specifically if it is the field 817 * set {@link #ALL}, {@link #ARGS}, {@link #GROUP}, or {@link #VALUES}. 818 * 819 * @return the substitution (type boolean) of this Fields object. 820 */ 821 public boolean isSubstitution() 822 { 823 return isAll() || isArguments() || isGroup() || isValues(); 824 } 825 826 private Comparable[] validate( Comparable[] fields ) 827 { 828 isOrdered = true; 829 830 Set<Comparable> names = new HashSet<Comparable>(); 831 832 for( int i = 0; i < fields.length; i++ ) 833 { 834 Comparable field = fields[ i ]; 835 836 if( !( field instanceof String || field instanceof Integer ) ) 837 throw new IllegalArgumentException( String.format( "invalid field type (%s); must be String or Integer: ", field ) ); 838 839 if( names.contains( field ) ) 840 throw new IllegalArgumentException( "duplicate field name found: " + field ); 841 842 names.add( field ); 843 844 if( field instanceof Number && (Integer) field != i ) 845 isOrdered = false; 846 } 847 848 return fields; 849 } 850 851 final Comparable[] get() 852 { 853 return fields; 854 } 855 856 /** 857 * Method get returns the field name or position at the given index i. 858 * 859 * @param i is of type int 860 * @return Comparable 861 */ 862 public final Comparable get( int i ) 863 { 864 return fields[ i ]; 865 } 866 867 final void set( int i, Comparable comparable ) 868 { 869 fields[ i ] = comparable; 870 871 if( isOrdered() && comparable instanceof Integer ) 872 isOrdered = i == (Integer) comparable; 873 } 874 875 /** 876 * Method getPos returns the pos array of this Fields object. 877 * 878 * @return the pos (type int[]) of this Fields object. 879 */ 880 public int[] getPos() 881 { 882 if( thisPos != null ) 883 return thisPos; // do not clone 884 885 if( isAll() || isUnknown() ) 886 thisPos = EMPTY_INT; 887 else 888 thisPos = makeThisPos(); 889 890 return thisPos; 891 } 892 893 /** 894 * Method hasRelativePos returns true if any ordinal position is relative (< 0) 895 * 896 * @return true if any ordinal position is relative (< 0) 897 */ 898 public boolean hasRelativePos() 899 { 900 for( int i : getPos() ) 901 { 902 if( i < 0 ) 903 return true; 904 } 905 906 return false; 907 } 908 909 private int[] makeThisPos() 910 { 911 int[] pos = new int[ size() ]; 912 913 for( int i = 0; i < size(); i++ ) 914 { 915 Comparable field = get( i ); 916 917 if( field instanceof Number ) 918 pos[ i ] = (Integer) field; 919 else 920 pos[ i ] = i; 921 } 922 923 return pos; 924 } 925 926 private final Map<Fields, int[]> getPosCache() 927 { 928 if( posCache == null ) 929 posCache = new HashMap<Fields, int[]>(); 930 931 return posCache; 932 } 933 934 private final int[] putReturn( Fields fields, int[] pos ) 935 { 936 getPosCache().put( fields, pos ); 937 938 return pos; 939 } 940 941 public final int[] getPos( Fields fields ) 942 { 943 return getPos( fields, -1 ); 944 } 945 946 final int[] getPos( Fields fields, int tupleSize ) 947 { 948 // test for key, as we stuff a null value 949 if( !isUnknown() && getPosCache().containsKey( fields ) ) 950 return getPosCache().get( fields ); 951 952 if( fields.isAll() ) 953 return putReturn( fields, null ); // return null, not getPos() 954 955 if( isAll() ) 956 return putReturn( fields, fields.getPos() ); 957 958 // don't cache unknown 959 if( size() == 0 && isUnknown() ) 960 return translatePos( fields, tupleSize ); 961 962 int[] pos = translatePos( fields, size() ); 963 964 return putReturn( fields, pos ); 965 } 966 967 private int[] translatePos( Fields fields, int fieldSize ) 968 { 969 int[] pos = new int[ fields.size() ]; 970 971 for( int i = 0; i < fields.size(); i++ ) 972 { 973 Comparable field = fields.get( i ); 974 975 if( field instanceof Number ) 976 pos[ i ] = translatePos( (Integer) field, fieldSize ); 977 else 978 pos[ i ] = indexOf( field ); 979 } 980 981 return pos; 982 } 983 984 final int translatePos( Integer integer ) 985 { 986 return translatePos( integer, size() ); 987 } 988 989 final int translatePos( Integer integer, int size ) 990 { 991 if( size == -1 ) 992 return integer; 993 994 if( integer < 0 ) 995 integer = size + integer; 996 997 if( !isUnknown() && ( integer >= size || integer < 0 ) ) 998 throw new TupleException( "position value is too large: " + integer + ", positions in field: " + size ); 999 1000 return integer; 1001 } 1002 1003 /** 1004 * Method getPos returns the index of the give field value in this Fields instance. The index corresponds to the 1005 * Tuple value index in an associated Tuple instance. 1006 * 1007 * @param fieldName of type Comparable 1008 * @return int 1009 */ 1010 public int getPos( Comparable fieldName ) 1011 { 1012 if( fieldName instanceof Number ) 1013 return translatePos( (Integer) fieldName ); 1014 else 1015 return indexOf( fieldName ); 1016 } 1017 1018 private final Map<Comparable, Integer> getIndex() 1019 { 1020 if( index != null ) 1021 return index; 1022 1023 // make thread-safe by not having invalid intermediate state 1024 Map<Comparable, Integer> local = new HashMap<Comparable, Integer>(); 1025 1026 for( int i = 0; i < size(); i++ ) 1027 local.put( get( i ), i ); 1028 1029 return index = local; 1030 } 1031 1032 private int indexOf( Comparable fieldName ) 1033 { 1034 Integer result = getIndex().get( fieldName ); 1035 1036 if( result == null ) 1037 throw new FieldsResolverException( this, new Fields( fieldName ) ); 1038 1039 return result; 1040 } 1041 1042 int indexOfSafe( Comparable fieldName ) 1043 { 1044 Integer result = getIndex().get( fieldName ); 1045 1046 if( result == null ) 1047 return -1; 1048 1049 return result; 1050 } 1051 1052 /** 1053 * Method iterator return an unmodifiable iterator of field values. if {@link #isSubstitution()} returns true, 1054 * this iterator will be empty. 1055 * 1056 * @return Iterator 1057 */ 1058 public Iterator iterator() 1059 { 1060 return Collections.unmodifiableList( Arrays.asList( fields ) ).iterator(); 1061 } 1062 1063 /** 1064 * Method select returns a new Fields instance with fields specified by the given selector. 1065 * 1066 * @param selector of type Fields 1067 * @return Fields 1068 */ 1069 public Fields select( Fields selector ) 1070 { 1071 if( !isOrdered() ) 1072 throw new TupleException( "this fields instance can only be used as a selector" ); 1073 1074 if( selector.isAll() ) 1075 return this; 1076 1077 // supports -1_UNKNOWN_RETURNED 1078 // guarantees pos arguments remain selector positions, not absolute positions 1079 if( isUnknown() ) 1080 return asSelector( selector ); 1081 1082 if( selector.isNone() ) 1083 return NONE; 1084 1085 Fields result = size( selector.size() ); 1086 1087 for( int i = 0; i < selector.size(); i++ ) 1088 { 1089 Comparable field = selector.get( i ); 1090 1091 if( field instanceof String ) 1092 { 1093 result.set( i, get( indexOf( field ) ) ); 1094 continue; 1095 } 1096 1097 int pos = translatePos( (Integer) field ); 1098 1099 if( this.get( pos ) instanceof String ) 1100 result.set( i, this.get( pos ) ); 1101 else 1102 result.set( i, pos ); // use absolute position if no field name 1103 } 1104 1105 if( this.types != null ) 1106 { 1107 result.types = new Type[ result.size() ]; 1108 1109 for( int i = 0; i < selector.size(); i++ ) 1110 { 1111 Comparable field = selector.get( i ); 1112 1113 if( field instanceof String ) 1114 result.setType( i, getType( indexOf( field ) ) ); 1115 else 1116 result.setType( i, getType( translatePos( (Integer) field ) ) ); 1117 } 1118 } 1119 1120 return result; 1121 } 1122 1123 /** 1124 * Method selectPos returns a Fields instance with only positional fields, no field names. 1125 * 1126 * @param selector of type Fields 1127 * @return Fields instance with only positions. 1128 */ 1129 public Fields selectPos( Fields selector ) 1130 { 1131 return selectPos( selector, 0 ); 1132 } 1133 1134 /** 1135 * Method selectPos returns a Fields instance with only positional fields, offset by given offset value, no field names. 1136 * 1137 * @param selector of type Fields 1138 * @param offset of type int 1139 * @return Fields instance with only positions. 1140 */ 1141 public Fields selectPos( Fields selector, int offset ) 1142 { 1143 int[] pos = getPos( selector ); 1144 1145 Fields results = size( pos.length ); 1146 1147 for( int i = 0; i < pos.length; i++ ) 1148 results.fields[ i ] = pos[ i ] + offset; 1149 1150 return results; 1151 } 1152 1153 /** 1154 * Method subtract returns the difference between this instance and the given fields instance. 1155 * <p/> 1156 * See {@link #append(Fields)} for adding field names. 1157 * 1158 * @param fields of type Fields 1159 * @return Fields 1160 */ 1161 public Fields subtract( Fields fields ) 1162 { 1163 if( fields.isAll() ) 1164 return Fields.NONE; 1165 1166 if( fields.isNone() ) 1167 return this; 1168 1169 List<Comparable> list = new LinkedList<Comparable>(); 1170 Collections.addAll( list, this.get() ); 1171 int[] pos = getPos( fields, -1 ); 1172 1173 for( int i : pos ) 1174 list.set( i, null ); 1175 1176 Util.removeAllNulls( list ); 1177 1178 Type[] newTypes = null; 1179 1180 if( this.types != null ) 1181 { 1182 List<Type> types = new LinkedList<Type>(); 1183 Collections.addAll( types, this.types ); 1184 1185 for( int i : pos ) 1186 types.set( i, null ); 1187 1188 Util.removeAllNulls( types ); 1189 1190 newTypes = types.toArray( new Type[ types.size() ] ); 1191 } 1192 1193 return new Fields( list.toArray( new Comparable[ list.size() ] ), newTypes ); 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 @Override 1504 public String toString() 1505 { 1506 String string; 1507 1508 if( isOrdered() ) 1509 string = orderedToString(); 1510 else 1511 string = unorderedToString(); 1512 1513 if( types != null ) 1514 string += " | " + Util.join( Util.simpleTypeNames( types ), ", " ); 1515 1516 return string; 1517 } 1518 1519 private String orderedToString() 1520 { 1521 StringBuffer buffer = new StringBuffer(); 1522 1523 if( size() != 0 ) 1524 { 1525 int startIndex = get( 0 ) instanceof Number ? (Integer) get( 0 ) : 0; 1526 1527 for( int i = 0; i < size(); i++ ) 1528 { 1529 Comparable field = get( i ); 1530 1531 if( field instanceof Number ) 1532 { 1533 if( i + 1 == size() || !( get( i + 1 ) instanceof Number ) ) 1534 { 1535 if( buffer.length() != 0 ) 1536 buffer.append( ", " ); 1537 1538 if( startIndex != i ) 1539 buffer.append( startIndex ).append( ":" ).append( field ); 1540 else 1541 buffer.append( i ); 1542 1543 startIndex = i; 1544 } 1545 1546 continue; 1547 } 1548 1549 if( i != 0 ) 1550 buffer.append( ", " ); 1551 1552 if( field instanceof String ) 1553 buffer.append( "\'" ).append( field ).append( "\'" ); 1554 else if( field instanceof Fields ) 1555 buffer.append( ( (Fields) field ).print() ); 1556 1557 startIndex = i + 1; 1558 } 1559 } 1560 1561 if( kind != null ) 1562 { 1563 if( buffer.length() != 0 ) 1564 buffer.append( ", " ); 1565 buffer.append( kind ); 1566 } 1567 1568 return buffer.toString(); 1569 } 1570 1571 private String unorderedToString() 1572 { 1573 StringBuffer buffer = new StringBuffer(); 1574 1575 for( Object field : get() ) 1576 { 1577 if( buffer.length() != 0 ) 1578 buffer.append( ", " ); 1579 1580 if( field instanceof String ) 1581 buffer.append( "\'" ).append( field ).append( "\'" ); 1582 else if( field instanceof Fields ) 1583 buffer.append( ( (Fields) field ).print() ); 1584 else 1585 buffer.append( field ); 1586 } 1587 1588 if( kind != null ) 1589 { 1590 if( buffer.length() != 0 ) 1591 buffer.append( ", " ); 1592 buffer.append( kind ); 1593 } 1594 1595 return buffer.toString(); 1596 } 1597 1598 /** 1599 * Method size returns the number of field positions in this instance. 1600 * 1601 * @return int 1602 */ 1603 public final int size() 1604 { 1605 return fields.length; 1606 } 1607 1608 /** 1609 * Method applyType should be used to associate a {@link java.lang.reflect.Type} with a given field name or position. 1610 * A new instance of Fields will be returned, this instance will not be modified. 1611 * <p/> 1612 * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will 1613 * be considered. 1614 * 1615 * @param fieldName of type Comparable 1616 * @param type of type Type 1617 */ 1618 public Fields applyType( Comparable fieldName, Type type ) 1619 { 1620 if( type == null ) 1621 throw new IllegalArgumentException( "given type must not be null" ); 1622 1623 int pos; 1624 1625 try 1626 { 1627 pos = getPos( asFieldName( fieldName ) ); 1628 } 1629 catch( FieldsResolverException exception ) 1630 { 1631 throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception ); 1632 } 1633 1634 Fields results = new Fields( fields ); 1635 1636 results.types = this.types == null ? new Type[ size() ] : copyTypes( this.types, this.types.length ); 1637 results.types[ pos ] = type; 1638 1639 return results; 1640 } 1641 1642 /** 1643 * Method applyType should be used to associate {@link java.lang.reflect.Type} with a given field name or position 1644 * as declared in the given Fields parameter. 1645 * <p/> 1646 * A new instance of Fields will be returned, this instance will not be modified. 1647 * <p/> 1648 * 1649 * @param fields of type Fields 1650 */ 1651 public Fields applyTypes( Fields fields ) 1652 { 1653 Fields result = new Fields( this.fields, this.types ); 1654 1655 for( Comparable field : fields ) 1656 result = result.applyType( field, fields.getType( fields.getPos( field ) ) ); 1657 1658 return result; 1659 } 1660 1661 /** 1662 * Method applyTypes returns a new Fields instance with the given types, replacing any existing type 1663 * information within the new instance. 1664 * <p/> 1665 * The Class array must be the same length as the number for fields in this instance. 1666 * 1667 * @param types the class types of this Fields object. 1668 * @return returns a new instance of Fields with this instances field names and the given types 1669 */ 1670 public Fields applyTypes( Type... types ) 1671 { 1672 Fields result = new Fields( fields ); 1673 1674 if( types == null ) // allows for type erasure 1675 return result; 1676 1677 if( types.length != size() ) 1678 throw new IllegalArgumentException( "given number of class instances must match fields size" ); 1679 1680 for( Type type : types ) 1681 { 1682 if( type == null ) 1683 throw new IllegalArgumentException( "type must not be null" ); 1684 } 1685 1686 result.types = copyTypes( types, types.length ); // make copy as Class[] could be passed in 1687 1688 return result; 1689 } 1690 1691 /** 1692 * Returns the Type at the given position or having the fieldName. 1693 * 1694 * @param fieldName of type String or Number 1695 * @return the Type 1696 */ 1697 public Type getType( Comparable fieldName ) 1698 { 1699 if( !hasTypes() ) 1700 return null; 1701 1702 return getType( getPos( fieldName ) ); 1703 } 1704 1705 public Type getType( int pos ) 1706 { 1707 if( !hasTypes() ) 1708 return null; 1709 1710 return this.types[ pos ]; 1711 } 1712 1713 /** 1714 * Returns the Class for the given position value. 1715 * <p/> 1716 * If the underlying value is of type {@link CoercibleType}, the result of 1717 * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned. 1718 * 1719 * @param fieldName of type String or Number 1720 * @return type Class 1721 */ 1722 public Class getTypeClass( Comparable fieldName ) 1723 { 1724 if( !hasTypes() ) 1725 return null; 1726 1727 return getTypeClass( getPos( fieldName ) ); 1728 } 1729 1730 public Class getTypeClass( int pos ) 1731 { 1732 Type type = getType( pos ); 1733 1734 if( type instanceof CoercibleType ) 1735 return ( (CoercibleType) type ).getCanonicalType(); 1736 1737 return (Class) type; 1738 } 1739 1740 protected void setType( int pos, Type type ) 1741 { 1742 if( type == null ) 1743 throw new IllegalArgumentException( "type may not be null" ); 1744 1745 this.types[ pos ] = type; 1746 } 1747 1748 /** 1749 * Returns a copy of the current types Type[] if any, else null. 1750 * 1751 * @return of type Type[] 1752 */ 1753 public Type[] getTypes() 1754 { 1755 return copyTypes( types, size() ); 1756 } 1757 1758 /** 1759 * Returns a copy of the current types Class[] if any, else null. 1760 * <p/> 1761 * If any underlying value is of type {@link CoercibleType}, the result of 1762 * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned. 1763 * 1764 * @return of type Class 1765 */ 1766 public Class[] getTypesClasses() 1767 { 1768 if( types == null ) 1769 return null; 1770 1771 Class[] classes = new Class[ types.length ]; 1772 1773 for( int i = 0; i < types.length; i++ ) 1774 { 1775 if( types[ i ] instanceof CoercibleType ) 1776 classes[ i ] = ( (CoercibleType) types[ i ] ).getCanonicalType(); 1777 else 1778 classes[ i ] = (Class) types[ i ]; // this throws a more helpful exception vs arraycopy 1779 } 1780 1781 return classes; 1782 } 1783 1784 private static Type[] copyTypes( Type[] types, int size ) 1785 { 1786 if( types == null ) 1787 return null; 1788 1789 Type[] copy = new Type[ size ]; 1790 1791 if( types.length != size ) 1792 throw new IllegalArgumentException( "types array must be same size as fields array" ); 1793 1794 System.arraycopy( types, 0, copy, 0, size ); 1795 1796 return copy; 1797 } 1798 1799 /** 1800 * Returns true if there are types associated with this instance. 1801 * 1802 * @return boolean 1803 */ 1804 public final boolean hasTypes() 1805 { 1806 return types != null; 1807 } 1808 1809 /** 1810 * Method setComparator should be used to associate a {@link java.util.Comparator} with a given field name or position. 1811 * <p/> 1812 * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will 1813 * be considered. 1814 * 1815 * @param fieldName of type Comparable 1816 * @param comparator of type Comparator 1817 */ 1818 public void setComparator( Comparable fieldName, Comparator comparator ) 1819 { 1820 if( !( comparator instanceof Serializable ) ) 1821 throw new IllegalArgumentException( "given comparator must be serializable" ); 1822 1823 if( comparators == null ) 1824 comparators = new Comparator[ size() ]; 1825 1826 try 1827 { 1828 comparators[ getPos( asFieldName( fieldName ) ) ] = comparator; 1829 } 1830 catch( FieldsResolverException exception ) 1831 { 1832 throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception ); 1833 } 1834 } 1835 1836 /** 1837 * Method setComparators sets all the comparators of this Fields object. The Comparator array 1838 * must be the same length as the number for fields in this instance. 1839 * 1840 * @param comparators the comparators of this Fields object. 1841 */ 1842 public void setComparators( Comparator... comparators ) 1843 { 1844 if( comparators.length != size() ) 1845 throw new IllegalArgumentException( "given number of comparator instances must match fields size" ); 1846 1847 for( Comparator comparator : comparators ) 1848 { 1849 if( !( comparator instanceof Serializable ) ) 1850 throw new IllegalArgumentException( "comparators must be serializable" ); 1851 } 1852 1853 this.comparators = comparators; 1854 } 1855 1856 protected static Comparable asFieldName( Comparable fieldName ) 1857 { 1858 if( fieldName instanceof Fields ) 1859 { 1860 Fields fields = (Fields) fieldName; 1861 1862 if( !fields.isDefined() ) 1863 throw new TupleException( "given Fields instance must explicitly declare one field name or position: " + fields.printVerbose() ); 1864 1865 fieldName = fields.get( 0 ); 1866 } 1867 1868 return fieldName; 1869 } 1870 1871 protected Comparator getComparator( Comparable fieldName ) 1872 { 1873 if( comparators == null ) 1874 return null; 1875 1876 try 1877 { 1878 return comparators[ getPos( asFieldName( fieldName ) ) ]; 1879 } 1880 catch( FieldsResolverException exception ) 1881 { 1882 return null; 1883 } 1884 } 1885 1886 /** 1887 * Method getComparators returns the comparators of this Fields object. 1888 * 1889 * @return the comparators (type Comparator[]) of this Fields object. 1890 */ 1891 public Comparator[] getComparators() 1892 { 1893 Comparator[] copy = new Comparator[ size() ]; 1894 1895 if( comparators != null ) 1896 System.arraycopy( comparators, 0, copy, 0, size() ); 1897 1898 return copy; 1899 } 1900 1901 /** 1902 * Method hasComparators test if this Fields instance has Comparators. 1903 * 1904 * @return boolean 1905 */ 1906 public boolean hasComparators() 1907 { 1908 return comparators != null; 1909 } 1910 1911 @Override 1912 public int compare( Tuple lhs, Tuple rhs ) 1913 { 1914 return lhs.compareTo( comparators, rhs ); 1915 } 1916 1917 @Override 1918 public boolean equals( Object object ) 1919 { 1920 if( this == object ) 1921 return true; 1922 if( object == null || getClass() != object.getClass() ) 1923 return false; 1924 1925 Fields fields = (Fields) object; 1926 1927 return equalsFields( fields ) && Arrays.equals( types, fields.types ); 1928 } 1929 1930 /** 1931 * Method equalsFields compares only the internal field names and postions only between this and the given Fields 1932 * instance. Type information is ignored. 1933 * 1934 * @param fields of type int 1935 * @return true if this and the given instance have the same positions and/or field names. 1936 */ 1937 public boolean equalsFields( Fields fields ) 1938 { 1939 return fields != null && this.kind == fields.kind && Arrays.equals( get(), fields.get() ); 1940 } 1941 1942 @Override 1943 public int hashCode() 1944 { 1945 if( hashCode == 0 ) 1946 hashCode = get() != null ? Arrays.hashCode( get() ) : 0; 1947 1948 return hashCode; 1949 } 1950 }