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