001 /* 002 * Copyright (c) 2007-2014 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 private int[] makeThisPos() 889 { 890 int[] pos = new int[ size() ]; 891 892 for( int i = 0; i < size(); i++ ) 893 { 894 Comparable field = get( i ); 895 896 if( field instanceof Number ) 897 pos[ i ] = (Integer) field; 898 else 899 pos[ i ] = i; 900 } 901 902 return pos; 903 } 904 905 private final Map<Fields, int[]> getPosCache() 906 { 907 if( posCache == null ) 908 posCache = new HashMap<Fields, int[]>(); 909 910 return posCache; 911 } 912 913 private final int[] putReturn( Fields fields, int[] pos ) 914 { 915 getPosCache().put( fields, pos ); 916 917 return pos; 918 } 919 920 public final int[] getPos( Fields fields ) 921 { 922 return getPos( fields, -1 ); 923 } 924 925 final int[] getPos( Fields fields, int tupleSize ) 926 { 927 // test for key, as we stuff a null value 928 if( !isUnknown() && getPosCache().containsKey( fields ) ) 929 return getPosCache().get( fields ); 930 931 if( fields.isAll() ) 932 return putReturn( fields, null ); // return null, not getPos() 933 934 if( isAll() ) 935 return putReturn( fields, fields.getPos() ); 936 937 // don't cache unknown 938 if( size() == 0 && isUnknown() ) 939 return translatePos( fields, tupleSize ); 940 941 int[] pos = translatePos( fields, size() ); 942 943 return putReturn( fields, pos ); 944 } 945 946 private int[] translatePos( Fields fields, int fieldSize ) 947 { 948 int[] pos = new int[ fields.size() ]; 949 950 for( int i = 0; i < fields.size(); i++ ) 951 { 952 Comparable field = fields.get( i ); 953 954 if( field instanceof Number ) 955 pos[ i ] = translatePos( (Integer) field, fieldSize ); 956 else 957 pos[ i ] = indexOf( field ); 958 } 959 960 return pos; 961 } 962 963 final int translatePos( Integer integer ) 964 { 965 return translatePos( integer, size() ); 966 } 967 968 final int translatePos( Integer integer, int size ) 969 { 970 if( size == -1 ) 971 return integer; 972 973 if( integer < 0 ) 974 integer = size + integer; 975 976 if( !isUnknown() && ( integer >= size || integer < 0 ) ) 977 throw new TupleException( "position value is too large: " + integer + ", positions in field: " + size ); 978 979 return integer; 980 } 981 982 /** 983 * Method getPos returns the index of the give field value in this Fields instance. The index corresponds to the 984 * Tuple value index in an associated Tuple instance. 985 * 986 * @param fieldName of type Comparable 987 * @return int 988 */ 989 public int getPos( Comparable fieldName ) 990 { 991 if( fieldName instanceof Number ) 992 return translatePos( (Integer) fieldName ); 993 else 994 return indexOf( fieldName ); 995 } 996 997 private final Map<Comparable, Integer> getIndex() 998 { 999 if( index != null ) 1000 return index; 1001 1002 // make thread-safe by not having invalid intermediate state 1003 Map<Comparable, Integer> local = new HashMap<Comparable, Integer>(); 1004 1005 for( int i = 0; i < size(); i++ ) 1006 local.put( get( i ), i ); 1007 1008 return index = local; 1009 } 1010 1011 private int indexOf( Comparable fieldName ) 1012 { 1013 Integer result = getIndex().get( fieldName ); 1014 1015 if( result == null ) 1016 throw new FieldsResolverException( this, new Fields( fieldName ) ); 1017 1018 return result; 1019 } 1020 1021 int indexOfSafe( Comparable fieldName ) 1022 { 1023 Integer result = getIndex().get( fieldName ); 1024 1025 if( result == null ) 1026 return -1; 1027 1028 return result; 1029 } 1030 1031 /** 1032 * Method iterator return an unmodifiable iterator of field values. if {@link #isSubstitution()} returns true, 1033 * this iterator will be empty. 1034 * 1035 * @return Iterator 1036 */ 1037 public Iterator iterator() 1038 { 1039 return Collections.unmodifiableList( Arrays.asList( fields ) ).iterator(); 1040 } 1041 1042 /** 1043 * Method select returns a new Fields instance with fields specified by the given selector. 1044 * 1045 * @param selector of type Fields 1046 * @return Fields 1047 */ 1048 public Fields select( Fields selector ) 1049 { 1050 if( !isOrdered() ) 1051 throw new TupleException( "this fields instance can only be used as a selector" ); 1052 1053 if( selector.isAll() ) 1054 return this; 1055 1056 // supports -1_UNKNOWN_RETURNED 1057 // guarantees pos arguments remain selector positions, not absolute positions 1058 if( isUnknown() ) 1059 return asSelector( selector ); 1060 1061 if( selector.isNone() ) 1062 return NONE; 1063 1064 Fields result = size( selector.size() ); 1065 1066 for( int i = 0; i < selector.size(); i++ ) 1067 { 1068 Comparable field = selector.get( i ); 1069 1070 if( field instanceof String ) 1071 { 1072 result.set( i, get( indexOf( field ) ) ); 1073 continue; 1074 } 1075 1076 int pos = translatePos( (Integer) field ); 1077 1078 if( this.get( pos ) instanceof String ) 1079 result.set( i, this.get( pos ) ); 1080 else 1081 result.set( i, pos ); // use absolute position if no field name 1082 } 1083 1084 if( this.types != null ) 1085 { 1086 result.types = new Type[ result.size() ]; 1087 1088 for( int i = 0; i < selector.size(); i++ ) 1089 { 1090 Comparable field = selector.get( i ); 1091 1092 if( field instanceof String ) 1093 result.setType( i, getType( indexOf( field ) ) ); 1094 else 1095 result.setType( i, getType( translatePos( (Integer) field ) ) ); 1096 } 1097 } 1098 1099 return result; 1100 } 1101 1102 /** 1103 * Method selectPos returns a Fields instance with only positional fields, no field names. 1104 * 1105 * @param selector of type Fields 1106 * @return Fields instance with only positions. 1107 */ 1108 public Fields selectPos( Fields selector ) 1109 { 1110 return selectPos( selector, 0 ); 1111 } 1112 1113 /** 1114 * Method selectPos returns a Fields instance with only positional fields, offset by given offset value, no field names. 1115 * 1116 * @param selector of type Fields 1117 * @param offset of type int 1118 * @return Fields instance with only positions. 1119 */ 1120 public Fields selectPos( Fields selector, int offset ) 1121 { 1122 int[] pos = getPos( selector ); 1123 1124 Fields results = size( pos.length ); 1125 1126 for( int i = 0; i < pos.length; i++ ) 1127 results.fields[ i ] = pos[ i ] + offset; 1128 1129 return results; 1130 } 1131 1132 /** 1133 * Method subtract returns the difference between this instance and the given fields instance. 1134 * <p/> 1135 * See {@link #append(Fields)} for adding field names. 1136 * 1137 * @param fields of type Fields 1138 * @return Fields 1139 */ 1140 public Fields subtract( Fields fields ) 1141 { 1142 if( fields.isAll() ) 1143 return Fields.NONE; 1144 1145 if( fields.isNone() ) 1146 return this; 1147 1148 List<Comparable> list = new LinkedList<Comparable>(); 1149 Collections.addAll( list, this.get() ); 1150 int[] pos = getPos( fields, -1 ); 1151 1152 for( int i : pos ) 1153 list.set( i, null ); 1154 1155 Util.removeAllNulls( list ); 1156 1157 Type[] newTypes = null; 1158 1159 if( this.types != null ) 1160 { 1161 List<Type> types = new LinkedList<Type>(); 1162 Collections.addAll( types, this.types ); 1163 1164 for( int i : pos ) 1165 types.set( i, null ); 1166 1167 Util.removeAllNulls( types ); 1168 1169 newTypes = types.toArray( new Type[ types.size() ] ); 1170 } 1171 1172 return new Fields( list.toArray( new Comparable[ list.size() ] ), newTypes ); 1173 } 1174 1175 /** 1176 * Method is used for appending the given Fields instance to this instance, into a new Fields instance. 1177 * <p/> 1178 * See {@link #subtract(Fields)} for removing field names. 1179 * <p/> 1180 * This method has been deprecated, see {@link #join(Fields...)} 1181 * 1182 * @param fields of type Fields[] 1183 * @return Fields 1184 */ 1185 @Deprecated 1186 public Fields append( Fields[] fields ) 1187 { 1188 if( fields.length == 0 ) 1189 return null; 1190 1191 Fields field = this; 1192 1193 for( Fields current : fields ) 1194 field = field.append( current ); 1195 1196 return field; 1197 } 1198 1199 /** 1200 * Method is used for appending the given Fields instance to this instance, into a new Fields instance suitable 1201 * for use as a field declaration. 1202 * <p/> 1203 * That is, any positional elements (including relative positions like {@code -1}, will be ignored during the 1204 * append. For example, the second {@code 0} position is lost in the result. 1205 * <p/> 1206 * {@code assert new Fields( 0, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 0, "a", 2, "b" )} 1207 * <p/> 1208 * See {@link #subtract(Fields)} for removing field names. 1209 * 1210 * @param fields of type Fields 1211 * @return Fields 1212 */ 1213 public Fields append( Fields fields ) 1214 { 1215 return appendInternal( fields, false ); 1216 } 1217 1218 /** 1219 * Method is used for appending the given Fields instance to this instance, into a new Fields instance 1220 * suitable for use as a field selector. 1221 * <p/> 1222 * That is, any positional elements will be retained during the append. For example, the {@code 5} and {@code 0} 1223 * are retained in the result. 1224 * <p/> 1225 * {@code assert new Fields( 5, "a" ).append( new Fields( 0, "b" ).equals( new Fields( 5, "a", 0, "b" )} 1226 * <p/> 1227 * Note any relative positional elements are retained, thus appending two Fields each declaring {@code -1} 1228 * position will result in a TupleException noting duplicate fields. 1229 * <p/> 1230 * See {@link #subtract(Fields)} for removing field names. 1231 * 1232 * @param fields of type Fields 1233 * @return Fields 1234 */ 1235 public Fields appendSelector( Fields fields ) 1236 { 1237 return appendInternal( fields, true ); 1238 } 1239 1240 private Fields appendInternal( Fields fields, boolean isSelect ) 1241 { 1242 if( fields == null ) 1243 return this; 1244 1245 // allow unordered fields to be appended to build more complex selectors 1246 if( this.isAll() || fields.isAll() ) 1247 throw new TupleException( "cannot append fields: " + this.print() + " + " + fields.print() ); 1248 1249 if( ( this.isUnknown() || this.size() == 0 ) && fields.isUnknown() ) 1250 return UNKNOWN; 1251 1252 if( fields.isNone() ) 1253 return this; 1254 1255 if( this.isNone() ) 1256 return fields; 1257 1258 Set<Comparable> names = new HashSet<Comparable>(); 1259 1260 // init the Field 1261 Fields result = size( this.size() + fields.size() ); 1262 1263 // copy over field names from this side 1264 copyRetain( names, result, this, 0, isSelect ); 1265 // copy over field names from that side 1266 copyRetain( names, result, fields, this.size(), isSelect ); 1267 1268 if( this.isUnknown() || fields.isUnknown() ) 1269 result.kind = Kind.UNKNOWN; 1270 1271 if( ( this.isNone() || this.types != null ) && fields.types != null ) 1272 { 1273 result.types = new Type[ this.size() + fields.size() ]; 1274 1275 if( this.types != null ) // supports appending to NONE 1276 System.arraycopy( this.types, 0, result.types, 0, this.size() ); 1277 1278 System.arraycopy( fields.types, 0, result.types, this.size(), fields.size() ); 1279 } 1280 1281 return result; 1282 } 1283 1284 /** 1285 * Method rename will rename the from fields to the values in to to fields. Fields may contain field names, or 1286 * positions. 1287 * <p/> 1288 * Using positions is useful to remove a field name put keep its place in the Tuple stream. 1289 * 1290 * @param from of type Fields 1291 * @param to of type Fields 1292 * @return Fields 1293 */ 1294 public Fields rename( Fields from, Fields to ) 1295 { 1296 if( this.isSubstitution() || this.isUnknown() ) 1297 throw new TupleException( "cannot rename fields in a substitution or unknown Fields instance: " + this.print() ); 1298 1299 if( from.size() != to.size() ) 1300 throw new TupleException( "from and to fields must be the same size" ); 1301 1302 if( from.isSubstitution() || from.isUnknown() ) 1303 throw new TupleException( "from fields may not be a substitution or unknown" ); 1304 1305 if( to.isSubstitution() || to.isUnknown() ) 1306 throw new TupleException( "to fields may not be a substitution or unknown" ); 1307 1308 Comparable[] newFields = Arrays.copyOf( this.fields, this.fields.length ); 1309 1310 int[] pos = getPos( from ); 1311 1312 for( int i = 0; i < pos.length; i++ ) 1313 newFields[ pos[ i ] ] = to.fields[ i ]; 1314 1315 Type[] newTypes = null; 1316 1317 if( this.types != null && to.types != null ) 1318 { 1319 newTypes = copyTypes( this.types, this.size() ); 1320 1321 for( int i = 0; i < pos.length; i++ ) 1322 newTypes[ pos[ i ] ] = to.types[ i ]; 1323 } 1324 1325 return new Fields( newFields, newTypes ); 1326 } 1327 1328 /** 1329 * Method project will return a new Fields instance similar to the given fields instance 1330 * except any absolute positional elements will be replaced by the current field names, if any. 1331 * 1332 * @param fields of type Fields 1333 * @return Fields 1334 */ 1335 public Fields project( Fields fields ) 1336 { 1337 if( fields == null ) 1338 return this; 1339 1340 Fields results = size( fields.size() ).applyTypes( fields.getTypes() ); 1341 1342 for( int i = 0; i < fields.fields.length; i++ ) 1343 { 1344 if( fields.fields[ i ] instanceof String ) 1345 results.fields[ i ] = fields.fields[ i ]; 1346 else if( this.fields[ i ] instanceof String ) 1347 results.fields[ i ] = this.fields[ i ]; 1348 else 1349 results.fields[ i ] = i; 1350 } 1351 1352 return results; 1353 } 1354 1355 private static void copy( Set<String> names, Fields result, Fields fields, int offset ) 1356 { 1357 for( int i = 0; i < fields.size(); i++ ) 1358 { 1359 Comparable field = fields.get( i ); 1360 1361 if( !( field instanceof String ) ) 1362 continue; 1363 1364 if( names != null ) 1365 { 1366 if( names.contains( field ) ) 1367 throw new TupleException( "field name already exists: " + field ); 1368 1369 names.add( (String) field ); 1370 } 1371 1372 result.set( i + offset, field ); 1373 } 1374 } 1375 1376 /** 1377 * Retains any relative positional elements like -1, but checks for duplicates 1378 * 1379 * @param names 1380 * @param result 1381 * @param fields 1382 * @param offset 1383 * @param isSelect 1384 */ 1385 private static void copyRetain( Set<Comparable> names, Fields result, Fields fields, int offset, boolean isSelect ) 1386 { 1387 for( int i = 0; i < fields.size(); i++ ) 1388 { 1389 Comparable field = fields.get( i ); 1390 1391 if( !isSelect && field instanceof Integer ) 1392 continue; 1393 1394 if( names != null ) 1395 { 1396 if( names.contains( field ) ) 1397 throw new TupleException( "field name already exists: " + field ); 1398 1399 names.add( field ); 1400 } 1401 1402 result.set( i + offset, field ); 1403 } 1404 } 1405 1406 /** 1407 * Method verifyContains tests if this instance contains the field names and positions specified in the given 1408 * fields instance. If the test fails, a {@link TupleException} is thrown. 1409 * 1410 * @param fields of type Fields 1411 * @throws TupleException when one or more fields are not contained in this instance. 1412 */ 1413 public void verifyContains( Fields fields ) 1414 { 1415 if( isUnknown() ) 1416 return; 1417 1418 try 1419 { 1420 getPos( fields ); 1421 } 1422 catch( TupleException exception ) 1423 { 1424 throw new TupleException( "these fields " + print() + ", do not contain " + fields.print() ); 1425 } 1426 } 1427 1428 /** 1429 * Method contains returns true if this instance contains the field names and positions specified in the given 1430 * fields instance. 1431 * 1432 * @param fields of type Fields 1433 * @return boolean 1434 */ 1435 public boolean contains( Fields fields ) 1436 { 1437 try 1438 { 1439 getPos( fields ); 1440 return true; 1441 } 1442 catch( Exception exception ) 1443 { 1444 return false; 1445 } 1446 } 1447 1448 /** 1449 * Method compareTo compares this instance to the given Fields instance. 1450 * 1451 * @param other of type Fields 1452 * @return int 1453 */ 1454 public int compareTo( Fields other ) 1455 { 1456 if( other.size() != size() ) 1457 return other.size() < size() ? 1 : -1; 1458 1459 for( int i = 0; i < size(); i++ ) 1460 { 1461 int c = get( i ).compareTo( other.get( i ) ); 1462 1463 if( c != 0 ) 1464 return c; 1465 } 1466 1467 return 0; 1468 } 1469 1470 /** 1471 * Method compareTo implements {@link Comparable#compareTo(Object)}. 1472 * 1473 * @param other of type Object 1474 * @return int 1475 */ 1476 public int compareTo( Object other ) 1477 { 1478 if( other instanceof Fields ) 1479 return compareTo( (Fields) other ); 1480 else 1481 return -1; 1482 } 1483 1484 /** 1485 * Method print returns a String representation of this instance. 1486 * 1487 * @return String 1488 */ 1489 public String print() 1490 { 1491 return "[" + toString() + "]"; 1492 } 1493 1494 /** 1495 * Method printLong returns a String representation of this instance along with the size. 1496 * 1497 * @return String 1498 */ 1499 public String printVerbose() 1500 { 1501 String fieldsString = toString(); 1502 1503 return "[{" + ( isDefined() ? size() : "?" ) + "}:" + fieldsString + "]"; 1504 } 1505 1506 1507 @Override 1508 public String toString() 1509 { 1510 String string; 1511 1512 if( isOrdered() ) 1513 string = orderedToString(); 1514 else 1515 string = unorderedToString(); 1516 1517 if( types != null ) 1518 string += " | " + Util.join( Util.simpleTypeNames( types ), ", " ); 1519 1520 return string; 1521 } 1522 1523 private String orderedToString() 1524 { 1525 StringBuffer buffer = new StringBuffer(); 1526 1527 if( size() != 0 ) 1528 { 1529 int startIndex = get( 0 ) instanceof Number ? (Integer) get( 0 ) : 0; 1530 1531 for( int i = 0; i < size(); i++ ) 1532 { 1533 Comparable field = get( i ); 1534 1535 if( field instanceof Number ) 1536 { 1537 if( i + 1 == size() || !( get( i + 1 ) instanceof Number ) ) 1538 { 1539 if( buffer.length() != 0 ) 1540 buffer.append( ", " ); 1541 1542 if( startIndex != i ) 1543 buffer.append( startIndex ).append( ":" ).append( field ); 1544 else 1545 buffer.append( i ); 1546 1547 startIndex = i; 1548 } 1549 1550 continue; 1551 } 1552 1553 if( i != 0 ) 1554 buffer.append( ", " ); 1555 1556 if( field instanceof String ) 1557 buffer.append( "\'" ).append( field ).append( "\'" ); 1558 else if( field instanceof Fields ) 1559 buffer.append( ( (Fields) field ).print() ); 1560 1561 startIndex = i + 1; 1562 } 1563 } 1564 1565 if( kind != null ) 1566 { 1567 if( buffer.length() != 0 ) 1568 buffer.append( ", " ); 1569 buffer.append( kind ); 1570 } 1571 1572 return buffer.toString(); 1573 } 1574 1575 private String unorderedToString() 1576 { 1577 StringBuffer buffer = new StringBuffer(); 1578 1579 for( Object field : get() ) 1580 { 1581 if( buffer.length() != 0 ) 1582 buffer.append( ", " ); 1583 1584 if( field instanceof String ) 1585 buffer.append( "\'" ).append( field ).append( "\'" ); 1586 else if( field instanceof Fields ) 1587 buffer.append( ( (Fields) field ).print() ); 1588 else 1589 buffer.append( field ); 1590 } 1591 1592 if( kind != null ) 1593 { 1594 if( buffer.length() != 0 ) 1595 buffer.append( ", " ); 1596 buffer.append( kind ); 1597 } 1598 1599 return buffer.toString(); 1600 } 1601 1602 /** 1603 * Method size returns the number of field positions in this instance. 1604 * 1605 * @return int 1606 */ 1607 public final int size() 1608 { 1609 return fields.length; 1610 } 1611 1612 /** 1613 * Method applyType should be used to associate a {@link java.lang.reflect.Type} with a given field name or position. 1614 * A new instance of Fields will be returned, this instance will not be modified. 1615 * <p/> 1616 * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will 1617 * be considered. 1618 * 1619 * @param fieldName of type Comparable 1620 * @param type of type Type 1621 */ 1622 public Fields applyType( Comparable fieldName, Type type ) 1623 { 1624 if( type == null ) 1625 throw new IllegalArgumentException( "given type must not be null" ); 1626 1627 int pos; 1628 1629 try 1630 { 1631 pos = getPos( asFieldName( fieldName ) ); 1632 } 1633 catch( FieldsResolverException exception ) 1634 { 1635 throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception ); 1636 } 1637 1638 Fields results = new Fields( fields ); 1639 1640 results.types = this.types == null ? new Type[ size() ] : this.types; 1641 results.types[ pos ] = type; 1642 1643 return results; 1644 } 1645 1646 /** 1647 * Method applyType should be used to associate {@link java.lang.reflect.Type} with a given field name or position 1648 * as declared in the given Fields parameter. 1649 * <p/> 1650 * A new instance of Fields will be returned, this instance will not be modified. 1651 * <p/> 1652 * 1653 * @param fields of type Fields 1654 */ 1655 public Fields applyTypes( Fields fields ) 1656 { 1657 Fields result = new Fields( this.fields, this.types ); 1658 1659 for( Comparable field : fields ) 1660 result = result.applyType( field, fields.getType( fields.getPos( field ) ) ); 1661 1662 return result; 1663 } 1664 1665 /** 1666 * Method applyTypes returns a new Fields instance with the given types, replacing any existing type 1667 * information within the new instance. 1668 * <p/> 1669 * The Class array must be the same length as the number for fields in this instance. 1670 * 1671 * @param types the class types of this Fields object. 1672 * @return returns a new instance of Fields with this instances field names and the given types 1673 */ 1674 public Fields applyTypes( Type... types ) 1675 { 1676 Fields result = new Fields( fields ); 1677 1678 if( types == null ) // allows for type erasure 1679 return result; 1680 1681 if( types.length != size() ) 1682 throw new IllegalArgumentException( "given number of class instances must match fields size" ); 1683 1684 for( Type type : types ) 1685 { 1686 if( type == null ) 1687 throw new IllegalArgumentException( "type must not be null" ); 1688 } 1689 1690 result.types = copyTypes( types, types.length ); // make copy as Class[] could be passed in 1691 1692 return result; 1693 } 1694 1695 /** 1696 * Returns the Type at the given position or having the fieldName. 1697 * 1698 * @param fieldName of type String or Number 1699 * @return the Type 1700 */ 1701 public Type getType( Comparable fieldName ) 1702 { 1703 if( !hasTypes() ) 1704 return null; 1705 1706 return getType( getPos( fieldName ) ); 1707 } 1708 1709 public Type getType( int pos ) 1710 { 1711 if( !hasTypes() ) 1712 return null; 1713 1714 return this.types[ pos ]; 1715 } 1716 1717 /** 1718 * Returns the Class for the given position value. 1719 * <p/> 1720 * If the underlying value is of type {@link CoercibleType}, the result of 1721 * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned. 1722 * 1723 * @param fieldName of type String or Number 1724 * @return type Class 1725 */ 1726 public Class getTypeClass( Comparable fieldName ) 1727 { 1728 if( !hasTypes() ) 1729 return null; 1730 1731 return getTypeClass( getPos( fieldName ) ); 1732 } 1733 1734 public Class getTypeClass( int pos ) 1735 { 1736 Type type = getType( pos ); 1737 1738 if( type instanceof CoercibleType ) 1739 return ( (CoercibleType) type ).getCanonicalType(); 1740 1741 return (Class) type; 1742 } 1743 1744 protected void setType( int pos, Type type ) 1745 { 1746 if( type == null ) 1747 throw new IllegalArgumentException( "type may not be null" ); 1748 1749 this.types[ pos ] = type; 1750 } 1751 1752 /** 1753 * Returns a copy of the current types Type[] if any, else null. 1754 * 1755 * @return of type Type[] 1756 */ 1757 public Type[] getTypes() 1758 { 1759 return copyTypes( types, size() ); 1760 } 1761 1762 /** 1763 * Returns a copy of the current types Class[] if any, else null. 1764 * <p/> 1765 * If any underlying value is of type {@link CoercibleType}, the result of 1766 * {@link cascading.tuple.type.CoercibleType#getCanonicalType()} will returned. 1767 * 1768 * @return of type Class 1769 */ 1770 public Class[] getTypesClasses() 1771 { 1772 if( types == null ) 1773 return null; 1774 1775 Class[] classes = new Class[ types.length ]; 1776 1777 for( int i = 0; i < types.length; i++ ) 1778 { 1779 if( types[ i ] instanceof CoercibleType ) 1780 classes[ i ] = ( (CoercibleType) types[ i ] ).getCanonicalType(); 1781 else 1782 classes[ i ] = (Class) types[ i ]; // this throws a more helpful exception vs arraycopy 1783 } 1784 1785 return classes; 1786 } 1787 1788 private static Type[] copyTypes( Type[] types, int size ) 1789 { 1790 if( types == null ) 1791 return null; 1792 1793 Type[] copy = new Type[ size ]; 1794 1795 if( types.length != size ) 1796 throw new IllegalArgumentException( "types array must be same size as fields array" ); 1797 1798 System.arraycopy( types, 0, copy, 0, size ); 1799 1800 return copy; 1801 } 1802 1803 /** 1804 * Returns true if there are types associated with this instance. 1805 * 1806 * @return boolean 1807 */ 1808 public final boolean hasTypes() 1809 { 1810 return types != null; 1811 } 1812 1813 /** 1814 * Method setComparator should be used to associate a {@link java.util.Comparator} with a given field name or position. 1815 * <p/> 1816 * {@code fieldName} may optionally be a {@link Fields} instance. Only the first field name or position will 1817 * be considered. 1818 * 1819 * @param fieldName of type Comparable 1820 * @param comparator of type Comparator 1821 */ 1822 public void setComparator( Comparable fieldName, Comparator comparator ) 1823 { 1824 if( !( comparator instanceof Serializable ) ) 1825 throw new IllegalArgumentException( "given comparator must be serializable" ); 1826 1827 if( comparators == null ) 1828 comparators = new Comparator[ size() ]; 1829 1830 try 1831 { 1832 comparators[ getPos( asFieldName( fieldName ) ) ] = comparator; 1833 } 1834 catch( FieldsResolverException exception ) 1835 { 1836 throw new IllegalArgumentException( "given field name was not found: " + fieldName, exception ); 1837 } 1838 } 1839 1840 /** 1841 * Method setComparators sets all the comparators of this Fields object. The Comparator array 1842 * must be the same length as the number for fields in this instance. 1843 * 1844 * @param comparators the comparators of this Fields object. 1845 */ 1846 public void setComparators( Comparator... comparators ) 1847 { 1848 if( comparators.length != size() ) 1849 throw new IllegalArgumentException( "given number of comparator instances must match fields size" ); 1850 1851 for( Comparator comparator : comparators ) 1852 { 1853 if( !( comparator instanceof Serializable ) ) 1854 throw new IllegalArgumentException( "comparators must be serializable" ); 1855 } 1856 1857 this.comparators = comparators; 1858 } 1859 1860 protected static Comparable asFieldName( Comparable fieldName ) 1861 { 1862 if( fieldName instanceof Fields ) 1863 { 1864 Fields fields = (Fields) fieldName; 1865 1866 if( !fields.isDefined() ) 1867 throw new TupleException( "given Fields instance must explicitly declare one field name or position: " + fields.printVerbose() ); 1868 1869 fieldName = fields.get( 0 ); 1870 } 1871 1872 return fieldName; 1873 } 1874 1875 protected Comparator getComparator( Comparable fieldName ) 1876 { 1877 if( comparators == null ) 1878 return null; 1879 1880 try 1881 { 1882 return comparators[ getPos( asFieldName( fieldName ) ) ]; 1883 } 1884 catch( FieldsResolverException exception ) 1885 { 1886 return null; 1887 } 1888 } 1889 1890 /** 1891 * Method getComparators returns the comparators of this Fields object. 1892 * 1893 * @return the comparators (type Comparator[]) of this Fields object. 1894 */ 1895 public Comparator[] getComparators() 1896 { 1897 Comparator[] copy = new Comparator[ size() ]; 1898 1899 if( comparators != null ) 1900 System.arraycopy( comparators, 0, copy, 0, size() ); 1901 1902 return copy; 1903 } 1904 1905 /** 1906 * Method hasComparators test if this Fields instance has Comparators. 1907 * 1908 * @return boolean 1909 */ 1910 public boolean hasComparators() 1911 { 1912 return comparators != null; 1913 } 1914 1915 @Override 1916 public int compare( Tuple lhs, Tuple rhs ) 1917 { 1918 return lhs.compareTo( comparators, rhs ); 1919 } 1920 1921 @Override 1922 public boolean equals( Object object ) 1923 { 1924 if( this == object ) 1925 return true; 1926 if( object == null || getClass() != object.getClass() ) 1927 return false; 1928 1929 Fields fields = (Fields) object; 1930 1931 return equalsFields( fields ) && Arrays.equals( types, fields.types ); 1932 } 1933 1934 /** 1935 * Method equalsFields compares only the internal field names and postions only between this and the given Fields 1936 * instance. Type information is ignored. 1937 * 1938 * @param fields of type int 1939 * @return true if this and the given instance have the same positions and/or field names. 1940 */ 1941 public boolean equalsFields( Fields fields ) 1942 { 1943 return fields != null && this.kind == fields.kind && Arrays.equals( get(), fields.get() ); 1944 } 1945 1946 @Override 1947 public int hashCode() 1948 { 1949 if( hashCode == 0 ) 1950 hashCode = get() != null ? Arrays.hashCode( get() ) : 0; 1951 1952 return hashCode; 1953 } 1954 }