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