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