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.pipe; 022 023 import java.lang.reflect.Type; 024 import java.util.ArrayList; 025 import java.util.Comparator; 026 import java.util.HashMap; 027 import java.util.HashSet; 028 import java.util.Iterator; 029 import java.util.LinkedHashMap; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.Set; 033 034 import cascading.flow.FlowElement; 035 import cascading.flow.planner.DeclaresResults; 036 import cascading.flow.planner.Scope; 037 import cascading.pipe.joiner.BufferJoin; 038 import cascading.pipe.joiner.InnerJoin; 039 import cascading.pipe.joiner.Joiner; 040 import cascading.tuple.Fields; 041 import cascading.tuple.FieldsResolverException; 042 import cascading.tuple.TupleException; 043 import cascading.tuple.coerce.Coercions; 044 import cascading.tuple.type.CoercibleType; 045 import cascading.util.Util; 046 047 import static java.util.Arrays.asList; 048 049 /** 050 * The base class for {@link GroupBy}, {@link CoGroup}, {@link Merge}, and {@link HashJoin}. This class should not be used directly. 051 * 052 * @see GroupBy 053 * @see CoGroup 054 * @see Merge 055 * @see HashJoin 056 */ 057 public class Splice extends Pipe 058 { 059 static enum Kind 060 { 061 GroupBy, CoGroup, Merge, Join 062 } 063 064 private Kind kind; 065 /** Field spliceName */ 066 private String spliceName; 067 /** Field pipes */ 068 private final List<Pipe> pipes = new ArrayList<Pipe>(); 069 /** Field groupFieldsMap */ 070 protected final Map<String, Fields> keyFieldsMap = new LinkedHashMap<String, Fields>(); // keep order 071 /** Field sortFieldsMap */ 072 protected Map<String, Fields> sortFieldsMap = new LinkedHashMap<String, Fields>(); // keep order 073 /** Field reverseOrder */ 074 private boolean reverseOrder = false; 075 /** Field declaredFields */ 076 protected Fields declaredFields; 077 /** Field resultGroupFields */ 078 protected Fields resultGroupFields; 079 /** Field repeat */ 080 private int numSelfJoins = 0; 081 /** Field coGrouper */ 082 private Joiner joiner; 083 084 /** Field pipePos */ 085 private transient Map<String, Integer> pipePos; 086 087 /** 088 * Constructor Splice creates a new Splice instance. 089 * 090 * @param lhs of type Pipe 091 * @param lhsGroupFields of type Fields 092 * @param rhs of type Pipe 093 * @param rhsGroupFields of type Fields 094 * @param declaredFields of type Fields 095 */ 096 protected Splice( Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Fields declaredFields ) 097 { 098 this( lhs, lhsGroupFields, rhs, rhsGroupFields, declaredFields, null, null ); 099 } 100 101 /** 102 * Constructor Splice creates a new Splice instance. 103 * 104 * @param lhs of type Pipe 105 * @param lhsGroupFields of type Fields 106 * @param rhs of type Pipe 107 * @param rhsGroupFields of type Fields 108 * @param declaredFields of type Fields 109 * @param resultGroupFields of type Fields 110 */ 111 protected Splice( Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Fields declaredFields, Fields resultGroupFields ) 112 { 113 this( lhs, lhsGroupFields, rhs, rhsGroupFields, declaredFields, resultGroupFields, null ); 114 } 115 116 /** 117 * Constructor Splice creates a new Splice instance. 118 * 119 * @param lhs of type Pipe 120 * @param lhsGroupFields of type Fields 121 * @param rhs of type Pipe 122 * @param rhsGroupFields of type Fields 123 * @param declaredFields of type Fields 124 * @param joiner of type CoGrouper 125 */ 126 protected Splice( Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Fields declaredFields, Joiner joiner ) 127 { 128 this( Pipe.pipes( lhs, rhs ), Fields.fields( lhsGroupFields, rhsGroupFields ), declaredFields, joiner ); 129 } 130 131 /** 132 * Constructor Splice creates a new Splice instance. 133 * 134 * @param lhs of type Pipe 135 * @param lhsGroupFields of type Fields 136 * @param rhs of type Pipe 137 * @param rhsGroupFields of type Fields 138 * @param declaredFields of type Fields 139 * @param resultGroupFields of type Fields 140 * @param joiner of type Joiner 141 */ 142 protected Splice( Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Fields declaredFields, Fields resultGroupFields, Joiner joiner ) 143 { 144 this( Pipe.pipes( lhs, rhs ), Fields.fields( lhsGroupFields, rhsGroupFields ), declaredFields, resultGroupFields, joiner ); 145 } 146 147 /** 148 * Constructor Splice creates a new Splice instance. 149 * 150 * @param lhs of type Pipe 151 * @param lhsGroupFields of type Fields 152 * @param rhs of type Pipe 153 * @param rhsGroupFields of type Fields 154 * @param joiner of type CoGrouper 155 */ 156 protected Splice( Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Joiner joiner ) 157 { 158 this( lhs, lhsGroupFields, rhs, rhsGroupFields, null, joiner ); 159 } 160 161 /** 162 * Constructor Splice creates a new Splice instance. 163 * 164 * @param lhs of type Pipe 165 * @param lhsGroupFields of type Fields 166 * @param rhs of type Pipe 167 * @param rhsGroupFields of type Fields 168 */ 169 protected Splice( Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields ) 170 { 171 this( Pipe.pipes( lhs, rhs ), Fields.fields( lhsGroupFields, rhsGroupFields ) ); 172 } 173 174 /** 175 * Constructor Splice creates a new Splice instance. 176 * 177 * @param pipes of type Pipe... 178 */ 179 protected Splice( Pipe... pipes ) 180 { 181 this( pipes, (Fields[]) null ); 182 } 183 184 /** 185 * Constructor Splice creates a new Splice instance. 186 * 187 * @param pipes of type Pipe[] 188 * @param groupFields of type Fields[] 189 */ 190 protected Splice( Pipe[] pipes, Fields[] groupFields ) 191 { 192 this( null, pipes, groupFields, null, null ); 193 } 194 195 196 /** 197 * Constructor Splice creates a new Splice instance. 198 * 199 * @param spliceName of type String 200 * @param pipes of type Pipe[] 201 * @param groupFields of type Fields[] 202 */ 203 protected Splice( String spliceName, Pipe[] pipes, Fields[] groupFields ) 204 { 205 this( spliceName, pipes, groupFields, null, null ); 206 } 207 208 /** 209 * Constructor Splice creates a new Splice instance. 210 * 211 * @param spliceName of type String 212 * @param pipes of type Pipe[] 213 * @param groupFields of type Fields[] 214 * @param declaredFields of type Fields 215 */ 216 protected Splice( String spliceName, Pipe[] pipes, Fields[] groupFields, Fields declaredFields ) 217 { 218 this( spliceName, pipes, groupFields, declaredFields, null ); 219 } 220 221 /** 222 * Constructor Splice creates a new Splice instance. 223 * 224 * @param spliceName of type String 225 * @param pipes of type Pipe[] 226 * @param groupFields of type Fields[] 227 * @param declaredFields of type Fields 228 * @param resultGroupFields of type Fields 229 */ 230 protected Splice( String spliceName, Pipe[] pipes, Fields[] groupFields, Fields declaredFields, Fields resultGroupFields ) 231 { 232 this( spliceName, pipes, groupFields, declaredFields, resultGroupFields, null ); 233 } 234 235 /** 236 * Constructor Splice creates a new Splice instance. 237 * 238 * @param pipes of type Pipe[] 239 * @param groupFields of type Fields[] 240 * @param declaredFields of type Fields 241 * @param joiner of type CoGrouper 242 */ 243 protected Splice( Pipe[] pipes, Fields[] groupFields, Fields declaredFields, Joiner joiner ) 244 { 245 this( null, pipes, groupFields, declaredFields, null, joiner ); 246 } 247 248 /** 249 * Constructor Splice creates a new Splice instance. 250 * 251 * @param pipes of type Pipe[] 252 * @param groupFields of type Fields[] 253 * @param declaredFields of type Fields 254 * @param resultGroupFields of type Fields 255 * @param joiner of type Joiner 256 */ 257 protected Splice( Pipe[] pipes, Fields[] groupFields, Fields declaredFields, Fields resultGroupFields, Joiner joiner ) 258 { 259 this( null, pipes, groupFields, declaredFields, resultGroupFields, joiner ); 260 } 261 262 /** 263 * Constructor Splice creates a new Splice instance. 264 * 265 * @param spliceName of type String 266 * @param pipes of type Pipe[] 267 * @param groupFields of type Fields[] 268 * @param declaredFields of type Fields 269 * @param joiner of type CoGrouper 270 */ 271 protected Splice( String spliceName, Pipe[] pipes, Fields[] groupFields, Fields declaredFields, Fields resultGroupFields, Joiner joiner ) 272 { 273 setKind(); 274 this.spliceName = spliceName; 275 276 int uniques = new HashSet<Pipe>( asList( Pipe.resolvePreviousAll( pipes ) ) ).size(); 277 278 if( pipes.length > 1 && uniques == 1 ) 279 { 280 if( new HashSet<Fields>( asList( groupFields ) ).size() != 1 ) 281 throw new IllegalArgumentException( "all groupFields must be identical" ); 282 283 addPipe( pipes[ 0 ] ); 284 this.numSelfJoins = pipes.length - 1; 285 this.keyFieldsMap.put( pipes[ 0 ].getName(), groupFields[ 0 ] ); 286 287 if( resultGroupFields != null && groupFields[ 0 ].size() * pipes.length != resultGroupFields.size() ) 288 throw new IllegalArgumentException( "resultGroupFields and cogroup joined fields must be same size" ); 289 } 290 else 291 { 292 int last = -1; 293 for( int i = 0; i < pipes.length; i++ ) 294 { 295 addPipe( pipes[ i ] ); 296 297 if( groupFields == null || groupFields.length == 0 ) 298 { 299 addGroupFields( pipes[ i ], Fields.FIRST ); 300 continue; 301 } 302 303 if( last != -1 && last != groupFields[ i ].size() ) 304 throw new IllegalArgumentException( "all groupFields must be same size" ); 305 306 last = groupFields[ i ].size(); 307 addGroupFields( pipes[ i ], groupFields[ i ] ); 308 } 309 310 if( resultGroupFields != null && last * pipes.length != resultGroupFields.size() ) 311 throw new IllegalArgumentException( "resultGroupFields and cogroup resulting joined fields must be same size" ); 312 } 313 314 this.declaredFields = declaredFields; 315 this.resultGroupFields = resultGroupFields; 316 this.joiner = joiner; 317 318 verifyCoGrouper(); 319 } 320 321 /** 322 * Constructor Splice creates a new Splice instance. 323 * 324 * @param spliceName of type String 325 * @param lhs of type Pipe 326 * @param lhsGroupFields of type Fields 327 * @param rhs of type Pipe 328 * @param rhsGroupFields of type Fields 329 * @param declaredFields of type Fields 330 */ 331 protected Splice( String spliceName, Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Fields declaredFields ) 332 { 333 this( lhs, lhsGroupFields, rhs, rhsGroupFields, declaredFields ); 334 this.spliceName = spliceName; 335 } 336 337 /** 338 * Constructor Splice creates a new Splice instance. 339 * 340 * @param spliceName of type String 341 * @param lhs of type Pipe 342 * @param lhsGroupFields of type Fields 343 * @param rhs of type Pipe 344 * @param rhsGroupFields of type Fields 345 * @param declaredFields of type Fields 346 * @param resultGroupFields of type Fields 347 */ 348 protected Splice( String spliceName, Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Fields declaredFields, Fields resultGroupFields ) 349 { 350 this( lhs, lhsGroupFields, rhs, rhsGroupFields, declaredFields, resultGroupFields ); 351 this.spliceName = spliceName; 352 } 353 354 /** 355 * Constructor Splice creates a new Splice instance. 356 * 357 * @param spliceName of type String 358 * @param lhs of type Pipe 359 * @param lhsGroupFields of type Fields 360 * @param rhs of type Pipe 361 * @param rhsGroupFields of type Fields 362 * @param declaredFields of type Fields 363 * @param joiner of type CoGrouper 364 */ 365 protected Splice( String spliceName, Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Fields declaredFields, Joiner joiner ) 366 { 367 this( lhs, lhsGroupFields, rhs, rhsGroupFields, declaredFields, joiner ); 368 this.spliceName = spliceName; 369 } 370 371 /** 372 * Constructor Splice creates a new Splice instance. 373 * 374 * @param spliceName of type String 375 * @param lhs of type Pipe 376 * @param lhsGroupFields of type Fields 377 * @param rhs of type Pipe 378 * @param rhsGroupFields of type Fields 379 * @param declaredFields of type Fields 380 * @param resultGroupFields of type Fields 381 * @param joiner of type Joiner 382 */ 383 protected Splice( String spliceName, Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Fields declaredFields, Fields resultGroupFields, Joiner joiner ) 384 { 385 this( lhs, lhsGroupFields, rhs, rhsGroupFields, declaredFields, resultGroupFields, joiner ); 386 this.spliceName = spliceName; 387 } 388 389 /** 390 * Constructor Splice creates a new Splice instance. 391 * 392 * @param spliceName of type String 393 * @param lhs of type Pipe 394 * @param lhsGroupFields of type Fields 395 * @param rhs of type Pipe 396 * @param rhsGroupFields of type Fields 397 * @param joiner of type CoGrouper 398 */ 399 protected Splice( String spliceName, Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields, Joiner joiner ) 400 { 401 this( lhs, lhsGroupFields, rhs, rhsGroupFields, joiner ); 402 this.spliceName = spliceName; 403 } 404 405 /** 406 * Constructor Splice creates a new Splice instance. 407 * 408 * @param spliceName of type String 409 * @param lhs of type Pipe 410 * @param lhsGroupFields of type Fields 411 * @param rhs of type Pipe 412 * @param rhsGroupFields of type Fields 413 */ 414 protected Splice( String spliceName, Pipe lhs, Fields lhsGroupFields, Pipe rhs, Fields rhsGroupFields ) 415 { 416 this( lhs, lhsGroupFields, rhs, rhsGroupFields ); 417 this.spliceName = spliceName; 418 } 419 420 /** 421 * Constructor Splice creates a new Splice instance. 422 * 423 * @param spliceName of type String 424 * @param pipes of type Pipe... 425 */ 426 protected Splice( String spliceName, Pipe... pipes ) 427 { 428 this( pipes ); 429 this.spliceName = spliceName; 430 } 431 432 /** 433 * Constructor Splice creates a new Splice instance. 434 * 435 * @param pipe of type Pipe 436 * @param groupFields of type Fields 437 * @param numSelfJoins of type int 438 * @param declaredFields of type Fields 439 */ 440 protected Splice( Pipe pipe, Fields groupFields, int numSelfJoins, Fields declaredFields ) 441 { 442 this( pipe, groupFields, numSelfJoins ); 443 this.declaredFields = declaredFields; 444 } 445 446 /** 447 * Constructor Splice creates a new Splice instance. 448 * 449 * @param pipe of type Pipe 450 * @param groupFields of type Fields 451 * @param numSelfJoins of type int 452 * @param declaredFields of type Fields 453 * @param resultGroupFields of type Fields 454 */ 455 protected Splice( Pipe pipe, Fields groupFields, int numSelfJoins, Fields declaredFields, Fields resultGroupFields ) 456 { 457 this( pipe, groupFields, numSelfJoins ); 458 this.declaredFields = declaredFields; 459 this.resultGroupFields = resultGroupFields; 460 461 if( resultGroupFields != null && groupFields.size() * numSelfJoins != resultGroupFields.size() ) 462 throw new IllegalArgumentException( "resultGroupFields and cogroup resulting join fields must be same size" ); 463 } 464 465 /** 466 * Constructor Splice creates a new Splice instance. 467 * 468 * @param pipe of type Pipe 469 * @param groupFields of type Fields 470 * @param numSelfJoins of type int 471 * @param declaredFields of type Fields 472 * @param joiner of type CoGrouper 473 */ 474 protected Splice( Pipe pipe, Fields groupFields, int numSelfJoins, Fields declaredFields, Joiner joiner ) 475 { 476 this( pipe, groupFields, numSelfJoins, declaredFields ); 477 this.joiner = joiner; 478 479 verifyCoGrouper(); 480 } 481 482 /** 483 * Constructor Splice creates a new Splice instance. 484 * 485 * @param pipe of type Pipe 486 * @param groupFields of type Fields 487 * @param numSelfJoins of type int 488 * @param declaredFields of type Fields 489 * @param resultGroupFields of type Fields 490 * @param joiner of type Joiner 491 */ 492 protected Splice( Pipe pipe, Fields groupFields, int numSelfJoins, Fields declaredFields, Fields resultGroupFields, Joiner joiner ) 493 { 494 this( pipe, groupFields, numSelfJoins, declaredFields, resultGroupFields ); 495 this.joiner = joiner; 496 497 verifyCoGrouper(); 498 } 499 500 /** 501 * Constructor Splice creates a new Splice instance. 502 * 503 * @param pipe of type Pipe 504 * @param groupFields of type Fields 505 * @param numSelfJoins of type int 506 * @param joiner of type CoGrouper 507 */ 508 protected Splice( Pipe pipe, Fields groupFields, int numSelfJoins, Joiner joiner ) 509 { 510 setKind(); 511 addPipe( pipe ); 512 this.keyFieldsMap.put( pipe.getName(), groupFields ); 513 this.numSelfJoins = numSelfJoins; 514 this.joiner = joiner; 515 516 verifyCoGrouper(); 517 } 518 519 /** 520 * Constructor Splice creates a new Splice instance. 521 * 522 * @param pipe of type Pipe 523 * @param groupFields of type Fields 524 * @param numSelfJoins of type int 525 */ 526 protected Splice( Pipe pipe, Fields groupFields, int numSelfJoins ) 527 { 528 this( pipe, groupFields, numSelfJoins, (Joiner) null ); 529 } 530 531 /** 532 * Constructor Splice creates a new Splice instance. 533 * 534 * @param spliceName of type String 535 * @param pipe of type Pipe 536 * @param groupFields of type Fields 537 * @param numSelfJoins of type int 538 * @param declaredFields of type Fields 539 */ 540 protected Splice( String spliceName, Pipe pipe, Fields groupFields, int numSelfJoins, Fields declaredFields ) 541 { 542 this( pipe, groupFields, numSelfJoins, declaredFields ); 543 this.spliceName = spliceName; 544 } 545 546 /** 547 * Constructor Splice creates a new Splice instance. 548 * 549 * @param spliceName of type String 550 * @param pipe of type Pipe 551 * @param groupFields of type Fields 552 * @param numSelfJoins of type int 553 * @param declaredFields of type Fields 554 * @param resultGroupFields of type Fields 555 */ 556 protected Splice( String spliceName, Pipe pipe, Fields groupFields, int numSelfJoins, Fields declaredFields, Fields resultGroupFields ) 557 { 558 this( pipe, groupFields, numSelfJoins, declaredFields, resultGroupFields ); 559 this.spliceName = spliceName; 560 } 561 562 /** 563 * Constructor Splice creates a new Splice instance. 564 * 565 * @param spliceName of type String 566 * @param pipe of type Pipe 567 * @param groupFields of type Fields 568 * @param numSelfJoins of type int 569 * @param declaredFields of type Fields 570 * @param joiner of type CoGrouper 571 */ 572 protected Splice( String spliceName, Pipe pipe, Fields groupFields, int numSelfJoins, Fields declaredFields, Joiner joiner ) 573 { 574 this( pipe, groupFields, numSelfJoins, declaredFields, joiner ); 575 this.spliceName = spliceName; 576 } 577 578 /** 579 * Constructor Splice creates a new Splice instance. 580 * 581 * @param spliceName of type String 582 * @param pipe of type Pipe 583 * @param groupFields of type Fields 584 * @param numSelfJoins of type int 585 * @param declaredFields of type Fields 586 * @param resultGroupFields of type Fields 587 * @param joiner of type Joiner 588 */ 589 protected Splice( String spliceName, Pipe pipe, Fields groupFields, int numSelfJoins, Fields declaredFields, Fields resultGroupFields, Joiner joiner ) 590 { 591 this( pipe, groupFields, numSelfJoins, declaredFields, resultGroupFields, joiner ); 592 this.spliceName = spliceName; 593 } 594 595 /** 596 * Constructor Splice creates a new Splice instance. 597 * 598 * @param spliceName of type String 599 * @param pipe of type Pipe 600 * @param groupFields of type Fields 601 * @param numSelfJoins of type int 602 * @param joiner of type CoGrouper 603 */ 604 protected Splice( String spliceName, Pipe pipe, Fields groupFields, int numSelfJoins, Joiner joiner ) 605 { 606 this( pipe, groupFields, numSelfJoins, joiner ); 607 this.spliceName = spliceName; 608 } 609 610 /** 611 * Constructor Splice creates a new Splice instance. 612 * 613 * @param spliceName of type String 614 * @param pipe of type Pipe 615 * @param groupFields of type Fields 616 * @param numSelfJoins of type int 617 */ 618 protected Splice( String spliceName, Pipe pipe, Fields groupFields, int numSelfJoins ) 619 { 620 this( pipe, groupFields, numSelfJoins ); 621 this.spliceName = spliceName; 622 } 623 624 //////////// 625 // GROUPBY 626 //////////// 627 628 /** 629 * Constructor Splice creates a new Splice instance where grouping occurs on {@link Fields#ALL} fields. 630 * 631 * @param pipe of type Pipe 632 */ 633 protected Splice( Pipe pipe ) 634 { 635 this( null, pipe, Fields.ALL, null, false ); 636 } 637 638 /** 639 * Constructor Splice creates a new Splice instance. 640 * 641 * @param pipe of type Pipe 642 * @param groupFields of type Fields 643 */ 644 protected Splice( Pipe pipe, Fields groupFields ) 645 { 646 this( null, pipe, groupFields, null, false ); 647 } 648 649 /** 650 * Constructor Splice creates a new Splice instance. 651 * 652 * @param spliceName of type String 653 * @param pipe of type Pipe 654 * @param groupFields of type Fields 655 */ 656 protected Splice( String spliceName, Pipe pipe, Fields groupFields ) 657 { 658 this( spliceName, pipe, groupFields, null, false ); 659 } 660 661 /** 662 * Constructor Splice creates a new Splice instance. 663 * 664 * @param pipe of type Pipe 665 * @param groupFields of type Fields 666 * @param sortFields of type Fields 667 */ 668 protected Splice( Pipe pipe, Fields groupFields, Fields sortFields ) 669 { 670 this( null, pipe, groupFields, sortFields, false ); 671 } 672 673 /** 674 * Constructor Splice creates a new Splice instance. 675 * 676 * @param spliceName of type String 677 * @param pipe of type Pipe 678 * @param groupFields of type Fields 679 * @param sortFields of type Fields 680 */ 681 protected Splice( String spliceName, Pipe pipe, Fields groupFields, Fields sortFields ) 682 { 683 this( spliceName, pipe, groupFields, sortFields, false ); 684 } 685 686 /** 687 * Constructor Splice creates a new Splice instance. 688 * 689 * @param pipe of type Pipe 690 * @param groupFields of type Fields 691 * @param sortFields of type Fields 692 * @param reverseOrder of type boolean 693 */ 694 protected Splice( Pipe pipe, Fields groupFields, Fields sortFields, boolean reverseOrder ) 695 { 696 this( null, pipe, groupFields, sortFields, reverseOrder ); 697 } 698 699 /** 700 * Constructor Splice creates a new Splice instance. 701 * 702 * @param spliceName of type String 703 * @param pipe of type Pipe 704 * @param groupFields of type Fields 705 * @param sortFields of type Fields 706 * @param reverseOrder of type boolean 707 */ 708 protected Splice( String spliceName, Pipe pipe, Fields groupFields, Fields sortFields, boolean reverseOrder ) 709 { 710 this( spliceName, Pipe.pipes( pipe ), groupFields, sortFields, reverseOrder ); 711 } 712 713 /** 714 * Constructor Splice creates a new Splice instance. 715 * 716 * @param pipes of type Pipe 717 * @param groupFields of type Fields 718 */ 719 protected Splice( Pipe[] pipes, Fields groupFields ) 720 { 721 this( null, pipes, groupFields, null, false ); 722 } 723 724 /** 725 * Constructor Splice creates a new Splice instance. 726 * 727 * @param spliceName of type String 728 * @param pipes of type Pipe 729 * @param groupFields of type Fields 730 */ 731 protected Splice( String spliceName, Pipe[] pipes, Fields groupFields ) 732 { 733 this( spliceName, pipes, groupFields, null, false ); 734 } 735 736 /** 737 * Constructor Splice creates a new Splice instance. 738 * 739 * @param pipes of type Pipe 740 * @param groupFields of type Fields 741 * @param sortFields of type Fields 742 */ 743 protected Splice( Pipe[] pipes, Fields groupFields, Fields sortFields ) 744 { 745 this( null, pipes, groupFields, sortFields, false ); 746 } 747 748 /** 749 * Constructor Splice creates a new Splice instance. 750 * 751 * @param spliceName of type String 752 * @param pipe of type Pipe 753 * @param groupFields of type Fields 754 * @param sortFields of type Fields 755 */ 756 protected Splice( String spliceName, Pipe[] pipe, Fields groupFields, Fields sortFields ) 757 { 758 this( spliceName, pipe, groupFields, sortFields, false ); 759 } 760 761 /** 762 * Constructor Splice creates a new Splice instance. 763 * 764 * @param pipes of type Pipe 765 * @param groupFields of type Fields 766 * @param sortFields of type Fields 767 * @param reverseOrder of type boolean 768 */ 769 protected Splice( Pipe[] pipes, Fields groupFields, Fields sortFields, boolean reverseOrder ) 770 { 771 this( null, pipes, groupFields, sortFields, reverseOrder ); 772 } 773 774 /** 775 * Constructor Splice creates a new Splice instance. 776 * 777 * @param spliceName of type String 778 * @param pipes of type Pipe[] 779 * @param groupFields of type Fields 780 * @param sortFields of type Fields 781 * @param reverseOrder of type boolean 782 */ 783 protected Splice( String spliceName, Pipe[] pipes, Fields groupFields, Fields sortFields, boolean reverseOrder ) 784 { 785 setKind(); 786 this.spliceName = spliceName; 787 788 for( Pipe pipe : pipes ) 789 { 790 addPipe( pipe ); 791 this.keyFieldsMap.put( pipe.getName(), groupFields ); 792 793 if( sortFields != null ) 794 this.sortFieldsMap.put( pipe.getName(), sortFields ); 795 } 796 797 this.reverseOrder = reverseOrder; 798 this.joiner = new InnerJoin(); 799 } 800 801 private void verifyCoGrouper() 802 { 803 if( isJoin() && joiner instanceof BufferJoin ) 804 throw new IllegalArgumentException( "invalid joiner, may not use BufferJoiner in a HashJoin" ); 805 806 if( joiner == null ) 807 { 808 joiner = new InnerJoin(); 809 return; 810 } 811 812 if( joiner.numJoins() == -1 ) 813 return; 814 815 int joins = Math.max( numSelfJoins, keyFieldsMap.size() - 1 ); // joining two streams is one join 816 817 if( joins != joiner.numJoins() ) 818 throw new IllegalArgumentException( "invalid joiner, only accepts " + joiner.numJoins() + " joins, there are: " + joins ); 819 } 820 821 private void setKind() 822 { 823 if( this instanceof GroupBy ) 824 kind = Kind.GroupBy; 825 else if( this instanceof CoGroup ) 826 kind = Kind.CoGroup; 827 else if( this instanceof Merge ) 828 kind = Kind.Merge; 829 else 830 kind = Kind.Join; 831 } 832 833 /** 834 * Method getDeclaredFields returns the declaredFields of this Splice object. 835 * 836 * @return the declaredFields (type Fields) of this Splice object. 837 */ 838 public Fields getDeclaredFields() 839 { 840 return declaredFields; 841 } 842 843 private void addPipe( Pipe pipe ) 844 { 845 if( pipe.getName() == null ) 846 throw new IllegalArgumentException( "each input pipe must have a name" ); 847 848 pipes.add( pipe ); // allow same pipe 849 } 850 851 private void addGroupFields( Pipe pipe, Fields fields ) 852 { 853 if( keyFieldsMap.containsKey( pipe.getName() ) ) 854 throw new IllegalArgumentException( "each input pipe branch must be uniquely named" ); 855 856 keyFieldsMap.put( pipe.getName(), fields ); 857 } 858 859 @Override 860 public String getName() 861 { 862 if( spliceName != null ) 863 return spliceName; 864 865 StringBuffer buffer = new StringBuffer(); 866 867 for( Pipe pipe : pipes ) 868 { 869 if( buffer.length() != 0 ) 870 { 871 if( isGroupBy() || isMerge() ) 872 buffer.append( "+" ); 873 else if( isCoGroup() || isJoin() ) 874 buffer.append( "*" ); // more semantically correct 875 } 876 877 buffer.append( pipe.getName() ); 878 } 879 880 spliceName = buffer.toString(); 881 882 return spliceName; 883 } 884 885 @Override 886 public Pipe[] getPrevious() 887 { 888 return pipes.toArray( new Pipe[ pipes.size() ] ); 889 } 890 891 /** 892 * Method getGroupingSelectors returns the groupingSelectors of this Splice object. 893 * 894 * @return the groupingSelectors (type Map<String, Fields>) of this Splice object. 895 */ 896 public Map<String, Fields> getKeySelectors() 897 { 898 return keyFieldsMap; 899 } 900 901 /** 902 * Method getSortingSelectors returns the sortingSelectors of this Splice object. 903 * 904 * @return the sortingSelectors (type Map<String, Fields>) of this Splice object. 905 */ 906 public Map<String, Fields> getSortingSelectors() 907 { 908 return sortFieldsMap; 909 } 910 911 /** 912 * Method isSorted returns true if this Splice instance is sorting values other than the group fields. 913 * 914 * @return the sorted (type boolean) of this Splice object. 915 */ 916 public boolean isSorted() 917 { 918 return !sortFieldsMap.isEmpty(); 919 } 920 921 /** 922 * Method isSortReversed returns true if sorting is reversed. 923 * 924 * @return the sortReversed (type boolean) of this Splice object. 925 */ 926 public boolean isSortReversed() 927 { 928 return reverseOrder; 929 } 930 931 public synchronized Map<String, Integer> getPipePos() 932 { 933 if( pipePos != null ) 934 return pipePos; 935 936 pipePos = new HashMap<String, Integer>(); 937 938 int pos = 0; 939 for( Object pipe : pipes ) 940 pipePos.put( ( (Pipe) pipe ).getName(), pos++ ); 941 942 return pipePos; 943 } 944 945 public Joiner getJoiner() 946 { 947 return joiner; 948 } 949 950 /** 951 * Method isGroupBy returns true if this Splice instance will perform a GroupBy operation. 952 * 953 * @return the groupBy (type boolean) of this Splice object. 954 */ 955 public final boolean isGroupBy() 956 { 957 return kind == Kind.GroupBy; 958 } 959 960 public final boolean isCoGroup() 961 { 962 return kind == Kind.CoGroup; 963 } 964 965 public final boolean isMerge() 966 { 967 return kind == Kind.Merge; 968 } 969 970 public final boolean isJoin() 971 { 972 return kind == Kind.Join; 973 } 974 975 public int getNumSelfJoins() 976 { 977 return numSelfJoins; 978 } 979 980 boolean isSelfJoin() 981 { 982 return numSelfJoins != 0; 983 } 984 985 // FIELDS 986 987 @Override 988 public Scope outgoingScopeFor( Set<Scope> incomingScopes ) 989 { 990 Map<String, Fields> groupingSelectors = resolveGroupingSelectors( incomingScopes ); 991 Map<String, Fields> sortingSelectors = resolveSortingSelectors( incomingScopes ); 992 Fields declared = resolveDeclared( incomingScopes ); 993 994 Fields outGroupingFields = resultGroupFields; 995 996 if( outGroupingFields == null && isCoGroup() ) 997 outGroupingFields = createJoinFields( incomingScopes, groupingSelectors, declared ); 998 999 // for Group, the outgoing fields are the same as those declared 1000 return new Scope( getName(), declared, outGroupingFields, groupingSelectors, sortingSelectors, declared, isGroupBy() ); 1001 } 1002 1003 private Fields createJoinFields( Set<Scope> incomingScopes, Map<String, Fields> groupingSelectors, Fields declared ) 1004 { 1005 if( declared.isNone() ) 1006 declared = Fields.UNKNOWN; 1007 1008 Map<String, Fields> incomingFields = new HashMap<String, Fields>(); 1009 1010 for( Scope scope : incomingScopes ) 1011 incomingFields.put( scope.getName(), scope.getIncomingSpliceFields() ); 1012 1013 Fields outGroupingFields = Fields.NONE; 1014 1015 int offset = 0; 1016 for( Pipe pipe : pipes ) // need to retain order of pipes 1017 { 1018 String pipeName = pipe.getName(); 1019 Fields pipeGroupingSelector = groupingSelectors.get( pipeName ); 1020 Fields incomingField = incomingFields.get( pipeName ); 1021 1022 if( !pipeGroupingSelector.isNone() ) 1023 { 1024 Fields offsetFields = incomingField.selectPos( pipeGroupingSelector, offset ); 1025 Fields resolvedSelect = declared.select( offsetFields ); 1026 1027 outGroupingFields = outGroupingFields.append( resolvedSelect ); 1028 } 1029 1030 offset += incomingField.size(); 1031 } 1032 1033 return outGroupingFields; 1034 } 1035 1036 Map<String, Fields> resolveGroupingSelectors( Set<Scope> incomingScopes ) 1037 { 1038 try 1039 { 1040 Map<String, Fields> groupingSelectors = getKeySelectors(); 1041 Map<String, Fields> groupingFields = resolveSelectorsAgainstIncoming( incomingScopes, groupingSelectors, "grouping" ); 1042 1043 if( !verifySameSize( groupingFields ) ) 1044 throw new OperatorException( this, "all grouping fields must be same size: " + toString() ); 1045 1046 verifySameTypes( groupingSelectors, groupingFields ); 1047 1048 return groupingFields; 1049 } 1050 catch( FieldsResolverException exception ) 1051 { 1052 throw new OperatorException( this, OperatorException.Kind.grouping, exception.getSourceFields(), exception.getSelectorFields(), exception ); 1053 } 1054 catch( RuntimeException exception ) 1055 { 1056 throw new OperatorException( this, "could not resolve grouping selector in: " + this, exception ); 1057 } 1058 } 1059 1060 private boolean verifySameTypes( Map<String, Fields> groupingSelectors, Map<String, Fields> groupingFields ) 1061 { 1062 // create array of field positions with comparators from the grouping selectors 1063 // unsure which side has the comparators declared so make a union 1064 boolean[] hasComparator = new boolean[ groupingFields.values().iterator().next().size() ]; 1065 1066 for( Map.Entry<String, Fields> entry : groupingSelectors.entrySet() ) 1067 { 1068 Comparator[] comparatorsArray = entry.getValue().getComparators(); 1069 1070 for( int i = 0; i < comparatorsArray.length; i++ ) 1071 hasComparator[ i ] = hasComparator[ i ] || comparatorsArray[ i ] != null; 1072 } 1073 1074 // compare all the rhs fields with the lhs (lhs and rhs are arbitrary here) 1075 Iterator<Fields> iterator = groupingFields.values().iterator(); 1076 Fields lhsFields = iterator.next(); 1077 Type[] lhsTypes = lhsFields.getTypes(); 1078 1079 // if types are null, no basis for comparison 1080 if( lhsTypes == null ) 1081 return true; 1082 1083 while( iterator.hasNext() ) 1084 { 1085 Fields rhsFields = iterator.next(); 1086 Type[] rhsTypes = rhsFields.getTypes(); 1087 1088 // if types are null, no basis for comparison 1089 if( rhsTypes == null ) 1090 return true; 1091 1092 for( int i = 0; i < lhsTypes.length; i++ ) 1093 { 1094 if( hasComparator[ i ] ) 1095 continue; 1096 1097 Type lhs = lhsTypes[ i ]; 1098 Type rhs = rhsTypes[ i ]; 1099 1100 lhs = getCanonicalType( lhs ); 1101 rhs = getCanonicalType( rhs ); 1102 1103 if( lhs.equals( rhs ) ) 1104 continue; 1105 1106 Fields lhsError = new Fields( lhsFields.get( i ), lhsFields.getType( i ) ); 1107 Fields rhsError = new Fields( rhsFields.get( i ), rhsFields.getType( i ) ); 1108 1109 throw new OperatorException( this, "grouping fields must declare same types:" + lhsError.printVerbose() + " not same as " + rhsError.printVerbose() ); 1110 } 1111 } 1112 1113 return true; 1114 } 1115 1116 private Type getCanonicalType( Type type ) 1117 { 1118 if( type instanceof CoercibleType ) 1119 type = ( (CoercibleType) type ).getCanonicalType(); 1120 1121 // if one side is primitive, normalize to its primitive wrapper type 1122 if( type instanceof Class ) 1123 type = Coercions.asNonPrimitive( (Class) type ); 1124 1125 return type; 1126 } 1127 1128 private boolean verifySameSize( Map<String, Fields> groupingFields ) 1129 { 1130 Iterator<Fields> iterator = groupingFields.values().iterator(); 1131 int size = iterator.next().size(); 1132 1133 while( iterator.hasNext() ) 1134 { 1135 Fields groupingField = iterator.next(); 1136 1137 if( groupingField.size() != size ) 1138 return false; 1139 1140 size = groupingField.size(); 1141 } 1142 1143 return true; 1144 } 1145 1146 private Map<String, Fields> resolveSelectorsAgainstIncoming( Set<Scope> incomingScopes, Map<String, Fields> selectors, String type ) 1147 { 1148 Map<String, Fields> resolvedFields = new HashMap<String, Fields>(); 1149 1150 for( Scope incomingScope : incomingScopes ) 1151 { 1152 Fields selector = selectors.get( incomingScope.getName() ); 1153 1154 if( selector == null ) 1155 throw new OperatorException( this, "no " + type + " selector found for: " + incomingScope.getName() ); 1156 1157 Fields incomingFields; 1158 1159 if( selector.isNone() ) 1160 incomingFields = Fields.NONE; 1161 else if( selector.isAll() ) 1162 incomingFields = incomingScope.getIncomingSpliceFields(); 1163 else if( selector.isGroup() ) 1164 incomingFields = incomingScope.getOutGroupingFields(); 1165 else if( selector.isValues() ) 1166 incomingFields = incomingScope.getOutValuesFields().subtract( incomingScope.getOutGroupingFields() ); 1167 else 1168 incomingFields = incomingScope.getIncomingSpliceFields().select( selector ); 1169 1170 resolvedFields.put( incomingScope.getName(), incomingFields ); 1171 } 1172 1173 return resolvedFields; 1174 } 1175 1176 Map<String, Fields> resolveSortingSelectors( Set<Scope> incomingScopes ) 1177 { 1178 try 1179 { 1180 if( getSortingSelectors().isEmpty() ) 1181 return null; 1182 1183 return resolveSelectorsAgainstIncoming( incomingScopes, getSortingSelectors(), "sorting" ); 1184 } 1185 catch( FieldsResolverException exception ) 1186 { 1187 throw new OperatorException( this, OperatorException.Kind.sorting, exception.getSourceFields(), exception.getSelectorFields(), exception ); 1188 } 1189 catch( RuntimeException exception ) 1190 { 1191 throw new OperatorException( this, "could not resolve sorting selector in: " + this, exception ); 1192 } 1193 } 1194 1195 @Override 1196 public Fields resolveIncomingOperationPassThroughFields( Scope incomingScope ) 1197 { 1198 return incomingScope.getIncomingSpliceFields(); 1199 } 1200 1201 Fields resolveDeclared( Set<Scope> incomingScopes ) 1202 { 1203 try 1204 { 1205 Fields declaredFields = getJoinDeclaredFields(); 1206 1207 // Fields.NONE is a flag to the CoGroup the following Buffer will use the JoinerClosure directly 1208 if( declaredFields != null && declaredFields.isNone() ) 1209 { 1210 if( !isCoGroup() ) 1211 throw new IllegalArgumentException( "Fields.NONE may only be declared as the join fields when using a CoGroup" ); 1212 1213 return Fields.NONE; 1214 } 1215 1216 if( declaredFields != null ) // null for GroupBy 1217 { 1218 if( incomingScopes.size() != pipes.size() && isSelfJoin() ) 1219 throw new OperatorException( this, "self joins without intermediate operators are not permitted, see 'numSelfJoins' constructor or identity function" ); 1220 1221 int size = 0; 1222 boolean foundUnknown = false; 1223 1224 List<Fields> appendableFields = getOrderedResolvedFields( incomingScopes ); 1225 1226 for( Fields fields : appendableFields ) 1227 { 1228 foundUnknown = foundUnknown || fields.isUnknown(); 1229 size += fields.size(); 1230 } 1231 1232 // we must relax field checking in the face of unknown fields 1233 if( !foundUnknown && declaredFields.size() != size * ( numSelfJoins + 1 ) ) 1234 { 1235 if( isSelfJoin() ) 1236 throw new OperatorException( this, "declared grouped fields not same size as grouped values, declared: " + declaredFields.printVerbose() + " != size: " + size * ( numSelfJoins + 1 ) ); 1237 else 1238 throw new OperatorException( this, "declared grouped fields not same size as grouped values, declared: " + declaredFields.printVerbose() + " resolved: " + Util.print( appendableFields, "" ) ); 1239 } 1240 1241 int i = 0; 1242 for( Fields appendableField : appendableFields ) 1243 { 1244 Type[] types = appendableField.getTypes(); 1245 1246 if( types == null ) 1247 { 1248 i += appendableField.size(); 1249 continue; 1250 } 1251 1252 for( Type type : types ) 1253 { 1254 if( type != null ) 1255 declaredFields = declaredFields.applyType( i, type ); 1256 1257 i++; 1258 } 1259 } 1260 1261 return declaredFields; 1262 } 1263 1264 // support merge or cogrouping here 1265 if( isGroupBy() || isMerge() ) 1266 { 1267 Iterator<Scope> iterator = incomingScopes.iterator(); 1268 Fields commonFields = iterator.next().getIncomingSpliceFields(); 1269 1270 while( iterator.hasNext() ) 1271 { 1272 Scope incomingScope = iterator.next(); 1273 Fields fields = incomingScope.getIncomingSpliceFields(); 1274 1275 if( !commonFields.equalsFields( fields ) ) 1276 throw new OperatorException( this, "merged streams must declare the same field names, in the same order, expected: " + commonFields.printVerbose() + " found: " + fields.printVerbose() ); 1277 } 1278 1279 return commonFields; 1280 } 1281 else 1282 { 1283 List<Fields> appendableFields = getOrderedResolvedFields( incomingScopes ); 1284 Fields appendedFields = new Fields(); 1285 1286 try 1287 { 1288 // will fail on name collisions 1289 for( Fields appendableField : appendableFields ) 1290 appendedFields = appendedFields.append( appendableField ); 1291 } 1292 catch( TupleException exception ) 1293 { 1294 String fields = ""; 1295 1296 for( Fields appendableField : appendableFields ) 1297 fields += appendableField.print(); 1298 1299 throw new OperatorException( this, "found duplicate field names in joined tuple stream: " + fields, exception ); 1300 } 1301 1302 return appendedFields; 1303 } 1304 } 1305 catch( OperatorException exception ) 1306 { 1307 throw exception; 1308 } 1309 catch( RuntimeException exception ) 1310 { 1311 throw new OperatorException( this, "could not resolve declared fields in: " + this, exception ); 1312 } 1313 } 1314 1315 public Fields getJoinDeclaredFields() 1316 { 1317 Fields declaredFields = getDeclaredFields(); 1318 1319 if( !( joiner instanceof DeclaresResults ) ) 1320 return declaredFields; 1321 1322 if( declaredFields == null && ( (DeclaresResults) joiner ).getFieldDeclaration() != null ) 1323 declaredFields = ( (DeclaresResults) joiner ).getFieldDeclaration(); 1324 1325 return declaredFields; 1326 } 1327 1328 private List<Fields> getOrderedResolvedFields( Set<Scope> incomingScopes ) 1329 { 1330 Map<String, Scope> scopesMap = new HashMap<String, Scope>(); 1331 1332 for( Scope incomingScope : incomingScopes ) 1333 scopesMap.put( incomingScope.getName(), incomingScope ); 1334 1335 List<Fields> appendableFields = new ArrayList<Fields>(); 1336 1337 for( Pipe pipe : pipes ) 1338 appendableFields.add( scopesMap.get( pipe.getName() ).getIncomingSpliceFields() ); 1339 return appendableFields; 1340 } 1341 1342 @Override 1343 public boolean isEquivalentTo( FlowElement element ) 1344 { 1345 boolean equivalentTo = super.isEquivalentTo( element ); 1346 1347 if( !equivalentTo ) 1348 return equivalentTo; 1349 1350 Splice splice = (Splice) element; 1351 1352 if( !keyFieldsMap.equals( splice.keyFieldsMap ) ) 1353 return false; 1354 1355 if( !pipes.equals( splice.pipes ) ) 1356 return false; 1357 1358 return true; 1359 } 1360 1361 // OBJECT OVERRIDES 1362 1363 @Override 1364 @SuppressWarnings({"RedundantIfStatement"}) 1365 public boolean equals( Object object ) 1366 { 1367 if( this == object ) 1368 return true; 1369 if( object == null || getClass() != object.getClass() ) 1370 return false; 1371 if( !super.equals( object ) ) 1372 return false; 1373 1374 Splice splice = (Splice) object; 1375 1376 if( spliceName != null ? !spliceName.equals( splice.spliceName ) : splice.spliceName != null ) 1377 return false; 1378 if( keyFieldsMap != null ? !keyFieldsMap.equals( splice.keyFieldsMap ) : splice.keyFieldsMap != null ) 1379 return false; 1380 if( pipes != null ? !pipes.equals( splice.pipes ) : splice.pipes != null ) 1381 return false; 1382 1383 return true; 1384 } 1385 1386 @Override 1387 public int hashCode() 1388 { 1389 int result = super.hashCode(); 1390 result = 31 * result + ( pipes != null ? pipes.hashCode() : 0 ); 1391 result = 31 * result + ( keyFieldsMap != null ? keyFieldsMap.hashCode() : 0 ); 1392 result = 31 * result + ( spliceName != null ? spliceName.hashCode() : 0 ); 1393 return result; 1394 } 1395 1396 @Override 1397 public String toString() 1398 { 1399 StringBuilder buffer = new StringBuilder( super.toString() ); 1400 1401 buffer.append( "[by:" ); 1402 1403 for( String name : keyFieldsMap.keySet() ) 1404 { 1405 if( keyFieldsMap.size() > 1 ) 1406 buffer.append( " " ).append( name ).append( ":" ); 1407 1408 buffer.append( keyFieldsMap.get( name ).printVerbose() ); 1409 } 1410 1411 if( isSelfJoin() ) 1412 buffer.append( "[numSelfJoins:" ).append( numSelfJoins ).append( "]" ); 1413 1414 buffer.append( "]" ); 1415 1416 return buffer.toString(); 1417 } 1418 1419 @Override 1420 protected void printInternal( StringBuffer buffer, Scope scope ) 1421 { 1422 super.printInternal( buffer, scope ); 1423 Map<String, Fields> map = scope.getKeySelectors(); 1424 1425 if( map != null ) 1426 { 1427 buffer.append( "[by:" ); 1428 1429 // important to retain incoming pipe order 1430 for( Map.Entry<String, Fields> entry : keyFieldsMap.entrySet() ) 1431 { 1432 String name = entry.getKey(); 1433 1434 if( map.size() > 1 ) 1435 buffer.append( name ).append( ":" ); 1436 1437 buffer.append( map.get( name ).print() ); // get resolved keys 1438 } 1439 1440 if( isSelfJoin() ) 1441 buffer.append( "[numSelfJoins:" ).append( numSelfJoins ).append( "]" ); 1442 1443 buffer.append( "]" ); 1444 } 1445 } 1446 }