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