1/* 2 * JSINQ, JavaScript integrated query 3 * Copyright (c) 2009 Kai Jäger. Some rights reserved. 4 * 5 * Use of this source code is governed by an MIT-style license that can be 6 * found in the license.txt file. 7 */ 8 9 if (typeof jsinq == 'undefined') { 10 jsinq = {}; 11 } 12 13(function() { 14 /** 15 * An exception that is thrown when an operation performed on an object was 16 * unsuccessful due to the state that the object was in. 17 * @constructor 18 */ 19 function InvalidOperationException() { } 20 InvalidOperationException.prototype = new Error(); 21 InvalidOperationException.prototype.name = 'InvalidOperationException'; 22 InvalidOperationException.prototype.message = 23 'Operation is not valid due to the current state of the object.'; 24 25 /** 26 * An exception that is thrown when the value of a parameter is outside the 27 * allowable range. 28 * @constructor 29 * @param parameter Name of the parameter that was out of range 30 */ 31 function ArgumentOutOfRangeException(parameter) { 32 this.message += parameter; 33 } 34 ArgumentOutOfRangeException.prototype = new Error(); 35 ArgumentOutOfRangeException.prototype.name = 'ArgumentOutOfRangeException'; 36 ArgumentOutOfRangeException.prototype.message = 37 'Specified argument was out of the range of valid values.\r\n' + 38 'Parameter name: '; 39 40 /** 41 * Provides a method to support the comparison of two objects for equality. 42 * @constructor 43 */ 44 function EqualityComparer() { } 45 EqualityComparer.prototype = { 46 /** 47 * Returns true if a and b are equal. 48 * @param a The first object 49 * @param b The second object 50 * @return True if a and b are equal 51 */ 52 equals: function(a, b) { 53 return a == b; 54 } 55 }; 56 57 (function() { 58 var defaultComparer = new EqualityComparer(); 59 /** 60 * Returns the default comparer which uses JavaScript's built-in non- 61 * strict equals-operator to compare two values for equality. 62 * @return The default comparer 63 */ 64 EqualityComparer.getDefault = function() { 65 return defaultComparer; 66 }; 67 })(); 68 69 /** 70 * Provides a method to support the comparison of two objects. 71 * @constructor 72 */ 73 function Comparer() { } 74 Comparer.prototype = { 75 /** 76 * Returns a value less than zero if a is less than b, a value greater 77 * zero if a is greater than b and zero if a equals b. 78 * @param a The first object 79 * @param b The second object 80 * @return A value indicating whether a is equal to, less than or 81 * greater than b 82 */ 83 compare: function(a, b) { 84 return a < b ? -1 : (a > b ? 1 : 0); 85 } 86 }; 87 88 (function() { 89 var defaultComparer = new Comparer(); 90 /** 91 * Returns the default comparer which uses JavaScript's built-in less- 92 * than and greater-than operators to compare two values. 93 * @return The default comparer 94 */ 95 Comparer.getDefault = function() { 96 return defaultComparer; 97 }; 98 })(); 99 100 /** 101 * Gives access to an Enumerator that can be used to enumerate the object. 102 * Invoke this constructor with either a scalar value to create a singleton 103 * list or with an array or a NodeList to make it enumerable. 104 * @constructor 105 * @param value The object to enumerate 106 */ 107 function Enumerable(value) { 108 if (arguments.length == 0) { 109 value = []; 110 } else if (typeof value.length == 'undefined' || (value.length > 0 && 111 typeof value[0] == 'undefined')) { 112 value = [value]; 113 } 114 115 /** 116 * Returns a new Enumerator 117 * @return A new enumerator 118 */ 119 this.getEnumerator = function() { 120 return new function() { 121 var index = -1; 122 123 /** 124 * Moves the internal cursor to the next value. Returns false 125 * if the cursor has been moved past the end of the collection. 126 * @return False if the cursor has been moved past the end of 127 * the collection 128 */ 129 this.moveNext = function() { 130 ++index; 131 return index < value.length; 132 }; 133 134 /** 135 * Returns the element in the collection that the internal 136 * cursor currently points to. Make sure to call moveNext 137 * before calling current. When calling current after an
138 * unsuccessful call to moveNext, the method will throw an 139 * exception. 140 * @return The current element 141 */ 142 this.current = function() { 143 if (index < 0 || index >= value.length) { 144 throw new InvalidOperationException(); 145 } 146 return value[index]; 147 }; 148 149 /** 150 * Places the internal cursor before the first element in the 151 * collection. 152 */ 153 this.reset = function() { 154 index = -1; 155 }; 156 }; 157 }; 158 } 159 160 /** 161 * Returns an empty Enumerable 162 * @return An empty Enumerable 163 */ 164 Enumerable.empty = function() { 165 return new Enumerable(); 166 }; 167 168 /** 169 * Returns an Enumerable for the specified range of numbers 170 */ 171 Enumerable.range = function(start, count) { 172 if (count < 0) { 173 throw new ArgumentOutOfRangeException(); 174 } 175 var func = function() { 176 this.getEnumerator = function() { 177 return new function() { 178 var index = -1; 179 var hasNext = false; 180 this.moveNext = function() { 181 hasNext = false; 182 if (index < count - 1) { 183 hasNext = true; 184 ++index; 185 return true; 186 } 187 return false; 188 }; 189 190 this.current = function() { 191 if (hasNext) { 192 return start + index; 193 } else { 194 throw new InvalidOperationException(); 195 } 196 }; 197 198 this.reset = function() { 199 index = -1; 200 }; 201 }; 202 }; 203 }; 204 func.prototype = Enumerable.prototype; 205 return new func(); 206 }; 207 208 /** 209 * Returns an Enumerable that contains the specified element "count" times 210 */ 211 Enumerable.repeat = function(element, count) { 212 if (count < 0) { 213 throw new ArgumentOutOfRangeException(); 214 } 215 var func = function() { 216 this.getEnumerator = function() { 217 return new function() { 218 var index = -1; 219 var hasNext = false; 220 this.moveNext = function() { 221 hasNext = false; 222 if (index < count - 1) { 223 hasNext = true; 224 ++index; 225 return true; 226 } 227 return false; 228 }; 229 230 this.current = function() { 231 if (hasNext) { 232 return element; 233 } else { 234 throw new InvalidOperationException(); 235 } 236 }; 237 238 this.reset = function() { 239 index = -1; 240 hasNext = false; 241 }; 242 }; 243 }; 244 }; 245 func.prototype = Enumerable.prototype; 246 return new func(); 247 }; 248 249 Enumerable.prototype = (function() { 250 // Identity function used as a default value for certain method 251 // overloads. See below. 252 function identity(value) { 253 return value; 254 } 255 256 // An associative container that maintains a list of key-value pairs. 257 // Since JavaScript's built-in associative arrays do not properly 258 // support complex types as keys and also lack support for custom 259 // comparators, this container is used instead. 260 // Todo: This is really slow when using complex types as keys. 261 function Hash(comparer) { 262 if (arguments.length == 0 || 263 arguments[0] == EqualityComparer.getDefault()) { 264 this.comparer = null; 265 } 266 this.comparer = arguments[0]; 267 this.primitiveItems = {}; 268 this.complexItems = []; 269 } 270 Hash.prototype = { 271 lookUp: function(key, func) { 272 var funcResult; 273 274 // Primitive keys are kept in an associative array, complex 275 // keys in a linear array 276 if (this.comparer == null && (typeof key == 'string' || 277 typeof key == 'number' || typeof key == 'boolean' || 278 typeof key == 'null')) { 279 var value = this.primitiveItems[key]; 280 if (typeof value != 'undefined') { 281 funcResult = func(value); 282 } else { 283 funcResult = func(); 284 } 285 if (funcResult.length > 1) { 286 this.primitiveItems[key] = funcResult[1]; 287 } 288 return funcResult[0]; 289 } else { 290 var length = this.complexItems.length; 291 var item; 292 for (var i = 0; i < length; i++) { 293 item = this.complexItems[i]; 294 if ((this.comparer != null && 295 this.comparer.equals(item.key, key)) || 296 (this.comparer == null && item.key == key)) { 297 funcResult = func(item.element); 298 if (funcResult.length > 1) { 299 item.element = funcResult[1]; 300 } 301 return funcResult[0]; 302 } 303 } 304 funcResult = func(); 305 if (funcResult.length > 1) { 306 this.complexItems.push({key: key, 307 element: funcResult[1]}); 308 } 309 return funcResult[0]; 310 } 311 }, 312 keyExists: function(key) { 313 return this.lookUp(key, function(value) { 314 return [arguments.length == 1]; 315 }); 316 }, 317 get: function(key) { 318 return this.lookUp(key, function(value) { 319 if (arguments.length == 0) { 320 throw false; 321 } 322 return [value]; 323 }); 324 }, 325 put: function(key, newValue, overwrite) { 326 if (arguments.length < 3) { 327 overwrite = false; 328 } 329 return this.lookUp(key, function(value) { 330 if (arguments.length == 0 || overwrite) { 331 return [true, newValue]; 332 } else { 333 return [false]; 334 } 335 }); 336 }, 337 toArray: function() { 338 var result = []; 339 for (var key in this.primitiveItems) { 340 result.push({key: key, element: this.primitiveItems[key]}); 341 } 342 return result.concat(this.complexItems); 343 }, 344 empty: function() { 345 this.primitiveItems = {}; 346 this.complexItems = []; 347 } 348 }; 349 350 // The two possible parameters for the orderBy function (see below) 351 var ASCENDING = 1; 352 var DESCENDING = -1; 353 354 // Implementation of Enumerable.orderBy, Enumerable.orderByDescending 355 // as well as OrderedEnumerable.thenBy and 356 // OrderedEnumerable.thenByDescending. This is implemented here, to 357 // avoid code duplication. The function returns a different 358 // implementation of the orderBy function based on the direction 359 // parameter. 360 function orderBy(direction) { 361 return function(keySelector) { 362 var _this = this; 363 var parentSelectors = []; 364 var comparer = Comparer.getDefault(); 365 if (arguments.length >= 3) { 366 comparer = arguments[1]; 367 parentSelectors = arguments[2]; 368 } else if (arguments.length >= 2) { 369 if (arguments[1] instanceof Array) { 370 parentSelectors = arguments[1]; 371 } else { 372 comparer = arguments[1]; 373 } 374 } 375 var selectors = [[keySelector, direction, comparer]]; 376 selectors = selectors.concat(parentSelectors); 377 378 var func = function() { 379 this.getEnumerator = function() { 380 return new function() { 381 var itemEnumerator = null; 382 383 function lazyInitialize() { 384 var array = _this.toArray(); 385 array.sort(function(a, b) { 386 var result = 0; 387 var selector; 388 var direction; 389 var comparer; 390 // Go through all selectors 391 for (var i = selectors.length - 1; 392 i >= 0; i--) { 393 result = 0; 394 selector = selectors[i][0]; 395 direction = selectors[i][1]; 396 comparer = selectors[i][2]; 397 var keyA = selector(a); 398 var keyB = selector(b); 399 var compareResult = comparer.compare( 400 keyA, keyB); 401 if (compareResult != 0) { 402 return compareResult * direction; 403 } 404 } 405 return 0; 406 });
407 itemEnumerator = (new Enumerable(array)). 408 getEnumerator(); 409 } 410 411 this.moveNext = function() { 412 if (itemEnumerator == null) { 413 lazyInitialize(); 414 } 415 return itemEnumerator.moveNext(); 416 }; 417 418 this.current = function() { 419 if (itemEnumerator == null) { 420 throw new InvalidOperationException(); 421 } 422 return itemEnumerator.current(); 423 }; 424 425 this.reset = function() { 426 if (itemEnumerator != null) { 427 itemEnumerator.reset(); 428 } 429 }; 430 }; 431 }; 432 433 // To avoid having to sort the Enumerable multiple times, 434 // thenBy does not actually perform any sorting at all. 435 // Instead, a chain of orderBy and thenBys essentially 436 // becomes an array of selectors. ThenBy then bypasses the 437 // previous orderBy and thenBys and returns a new 438 // Enumerator that sorts the original (unsorted) Enumerable 439 // directly using the array of selectors (See above). 440 this.thenBy = function(keySelector, comparer) { 441 var orderByFunc = orderBy(ASCENDING); 442 if (arguments.length < 2) { 443 comparer = Comparer.getDefault(); 444 } 445 return orderByFunc.call(_this, keySelector, comparer, 446 selectors); 447 }; 448 449 this.thenByDescending = function(keySelector, comparer) { 450 var orderByFunc = orderBy(DESCENDING); 451 if (arguments.length < 2) { 452 comparer = Comparer.getDefault(); 453 } 454 return orderByFunc.call(_this, keySelector, comparer, 455 selectors); 456 }; 457 }; 458 func.prototype = Enumerable.prototype; 459 return new func(); 460 }; 461 } 462 463 // Implementation of Enumerable.join and Enumerable.groupJoin. Again, 464 // this helps in avoiding code-duplication. 465 function join(group) { 466 return function (second, outerKeySelector, innerKeySelector, 467 resultSelector, comparer) { 468 var _this = this; 469 if (arguments.length < 5) { 470 comparer = EqualityComparer.getDefault(); 471 } 472 473 var func = function() { 474 this.getEnumerator = function() { 475 return new function() { 476 var hash = null; 477 var enumerator = _this.getEnumerator(); 478 var hasNext = false; 479 var firstElement; 480 var secondElement; 481 var secondList = null; 482 var index = -1; 483 484 function lazyInitialize() { 485 hash = new Hash(comparer); 486 var secondEnumerator = second.getEnumerator(); 487 while (secondEnumerator.moveNext()) { 488 var current = secondEnumerator.current(); 489 var key = innerKeySelector(current); 490 if (!hash.put(key, [current])) { 491 var array = hash.get(key); 492 array.push(current); 493 } 494 } 495 } 496 497 this.moveNext = function() { 498 if (hash == null) { 499 lazyInitialize(); 500 } 501 var current; 502 var key; 503 if (group) { 504 hasNext = enumerator.moveNext(); 505 if (hasNext) { 506 current = enumerator.current(); 507 key = outerKeySelector(current); 508 firstElement = current; 509 if (hash.keyExists(key)) { 510 secondElement = new Enumerable( 511 hash.get(key)); 512 } else { 513 secondElement = Enumerable.empty(); 514 } 515 } 516 } else { 517 if (secondList != null && ++index < 518 secondList.length) { 519 secondElement = secondList[index]; 520 hasNext = true; 521 } else { 522 hasNext = false; 523 while (enumerator.moveNext()) { 524 current = enumerator.current(); 525 key = outerKeySelector(current); 526 if (hash.keyExists(key)) { 527 secondList = hash.get(key); 528 secondElement = secondList[0]; 529 index = 0; 530 hasNext = true; 531 firstElement = current; 532 break; 533 } else { 534 continue; 535 } 536 } 537 } 538 } 539 return hasNext; 540 }; 541 542 this.current = function() { 543 if (!hasNext) { 544 throw new InvalidOperationException(); 545 } 546 return resultSelector(firstElement, 547 secondElement); 548 }; 549 550 this.reset = function() { 551 enumerator.reset(); 552 }; 553 }; 554 }; 555 }; 556 func.prototype = Enumerable.prototype; 557 return new func(); 558 }; 559 } 560 561 return { 562 /** 563 * Accumulates the elements of an Enumerable using an accumulator 564 * function. 565 * 566 * Overloads: 567 * aggregate(func) 568 * aggregate(seed, func) 569 * aggregate(seed, func, resultSelector) 570 */ 571 aggregate: function() { 572 var enumerator = this.getEnumerator(); 573 574 if (!enumerator.moveNext()) { 575 throw new InvalidOperationException(); 576 } 577 578 var running; 579 var func; 580 var resultSelector = identity; 581 if (arguments.length >= 2) { 582 running = arguments[0]; 583 func = arguments[1]; 584 if (arguments.length >= 3) { 585 resultSelector = arguments[2]; 586 } 587 enumerator.reset(); 588 } else { 589 func = arguments[0]; 590 running = enumerator.current(); 591 } 592 while (enumerator.moveNext()) { 593 running = func(running, enumerator.current()); 594 } 595 return resultSelector(running); 596 }, 597 598 /** 599 * Returns true if all elements in the Enumerable satisfy the 600 * specified condition. 601 */ 602 all: function(predicate) { 603 var enumerator = this.getEnumerator(); 604 while (enumerator.moveNext()) { 605 if (!predicate(enumerator.current())) { 606 return false; 607 } 608 } 609 return true; 610 }, 611 612 /** 613 * Returns true if the Enumerable contains any elements or if at 614 * least one of the elements in the Enumerable satisfy a specified 615 * condition. 616 * 617 * Overloads: 618 * any() 619 * any(predicate) 620 */ 621 any: function(predicate) { 622 var enumerator = this.getEnumerator(); 623 if (arguments.length == 0) { 624 return enumerator.moveNext(); 625 } 626 var array = []; 627 while (enumerator.moveNext()) { 628 if (predicate(enumerator.current())) { 629 return true; 630 } 631 } 632 return false; 633 }, 634 635 /** 636 * Returns the average of the elements in the Enumerable, 637 * optionally using the specified selector function. 638 * 639 * Overloads: 640 * average() 641 * average(selector) 642 */ 643 average: function(selector) { 644 if (arguments.length == 0) { 645 selector = identity; 646 } 647 var count = 0; 648 var sum = this.aggregate(0, function(running, current) { 649 ++count; 650 return running + selector(current); 651 }); 652 return sum / count; 653 }, 654 655 /** 656 * Creates a new Enumerable that is the result of the concatenation 657 * of two Enumerables. 658 */ 659 concat: function(second) { 660 var _this = this; 661 var func = function() { 662 this.getEnumerator = function() { 663 return new function() { 664 var firstEnumerator = _this.getEnumerator(); 665 var secondEnumerator = second.getEnumerator(); 666 var enumerator = firstEnumerator; 667 var canSwap = true; 668 this.moveNext = function() { 669 if (!enumerator.moveNext()) { 670 if (canSwap) { 671 enumerator = secondEnumerator; 672 return enumerator.moveNext(); 673 } 674 return false; 675 } 676 return true; 677 }; 678 679 this.current = function() { 680 return enumerator.current(); 681 }; 682 683 this.reset = function() { 684 canSwap = true; 685 firstEnumerator.reset(); 686 secondEnumerator.reset(); 687 enumerator = firstEnumerator; 688 }; 689 }; 690 }; 691 }; 692 func.prototype = Enumerable.prototype; 693 return new func(); 694 }, 695 696 /** 697 * Returns true if the Enumerable contains the specified element. 698 * Optionally uses the specified comparer. 699 * 700 * Overloads: 701 * contains(value) 702 * contains(value, comparer) 703 */ 704 contains: function(value, comparer) { 705 if (arguments.length == 1) { 706 comparer = EqualityComparer.getDefault(); 707 } 708 return this.any(function(item) { 709 return comparer.equals(item, value); 710 }); 711 }, 712 713 /** 714 * Returns the number of elements in the Enumerable. If a predicate 715 * function is specified, only those elements will be counted that 716 * satisfy the given condition. 717 */ 718 count: function(predicate) { 719 var count = 0; 720 var enumerator = this.getEnumerator(); 721 var hasPredicate = typeof predicate == 'function'; 722 while (enumerator.moveNext()) { 723 if ((hasPredicate && predicate(enumerator.current())) || 724 !hasPredicate) { 725 ++count; 726 } 727 } 728 return count; 729 }, 730 731 /** 732 * Returns the Enumerable or a new Enumerable containing only the 733 * specified default value, should the Enumerable be empty. 734 */ 735 defaultIfEmpty: function(defaultValue) { 736 var isEmpty = !this.any(function(item) { 737 return true; 738 }); 739 if (isEmpty) { 740 return new Enumerable(defaultValue); 741 } 742 return this; 743 }, 744 745 /** 746 * Returns a new Enumerable that contains only distinct elements. 747 * Optionally uses a specified comparer. 748 * 749 * Overloads: 750 * distinct() 751 * distinct(comparer) 752 */ 753 distinct: function(comparer) { 754 var _this = this; 755 var hasComparer = arguments.length > 0;
756 757 var func = function() { 758 this.getEnumerator = function() { 759 return new function() { 760 var enumerator = _this.getEnumerator(); 761 var hash; 762 if (hasComparer) { 763 hash = new Hash(comparer); 764 } else { 765 hash = new Hash(); 766 } 767 768 this.moveNext = function() { 769 while (enumerator.moveNext()) { 770 if (hash.put(enumerator.current(), true)) { 771 return true; 772 } 773 } 774 return false; 775 }; 776 777 this.current = function() { 778 return enumerator.current(); 779 }; 780 781 this.reset = function() { 782 enumerator.reset(); 783 hash.empty(); 784 }; 785 }; 786 }; 787 }; 788 func.prototype = Enumerable.prototype; 789 return new func(); 790 }, 791 792 /** 793 * Returns the element at the specified offset or throws an 794 * exception if the index is out of bounds. 795 */ 796 elementAt: function(index) { 797 var element; 798 var found = this.any(function(item) { 799 element = item; 800 return index-- == 0; 801 }); 802 if (!found) { 803 throw new ArgumentOutOfRangeException('index'); 804 } 805 return element; 806 }, 807 808 /** 809 * Same as elementAt, except it returns the specified defaultValue 810 * if the given index is out of bounds. This differs from the LINQ 811 * implementation in that the default value needs to be specified 812 * explicitly. 813 */ 814 elementAtOrDefault: function(index, defaultValue) { 815 try { 816 return this.elementAt(index); 817 } catch (e) { 818 return defaultValue; 819 } 820 }, 821 822 /** 823 * Returns a new Enumerable containing all elements from this 824 * Enumerable except those contained in a second Enumerable (i.e. 825 * the set difference between two enumerables). Optionally uses the 826 * specified comparer. 827 * 828 * Overloads: 829 * except(second) 830 * except(second, comparer) 831 */ 832 except: function(second, comparer) { 833 if (arguments.length < 2) { 834 comparer = EqualityComparer.getDefault(); 835 } 836 return this.where(function(item) { 837 return !second.any(function(compare) { 838 return comparer.equals(item, compare); 839 }); 840 }); 841 }, 842 843 /** 844 * Returns the first element in the Enumerable or throws an 845 * exception if the Enumerable is empty. If a predicate function 846 * is specified, the method returns the first element that 847 * satisfies the given condition. If the Enumerable is empty or no 848 * element satisfies the specified condition, an exception is 849 * thrown. 850 * 851 * Overloads: 852 * first() 853 * first(predicate) 854 */ 855 first: function(predicate) { 856 if (arguments.length == 0) { 857 try { 858 return this.elementAt(0); 859 } catch (e) { 860 throw new InvalidOperationException(); 861 } 862 } else { 863 var element; 864 var found = this.any(function(item) { 865 if (predicate(item)) { 866 element = item; 867 return true; 868 } 869 return false; 870 }); 871 if (!found) { 872 throw new InvalidOperationException(); 873 } 874 return element; 875 } 876 }, 877 878 /** 879 * Same as first, except it returns a specified defaultValue if 880 * the Enumerable is empty or no element satisfies the specified 881 * condition. This method is different from its LINQ counterpart 882 * in that it requires the default value to be specified 883 * explicitly. 884 * 885 * Overloads: 886 * firstOrDefault(defaultValue) 887 * firstOrDefault(predicate, defaultValue) 888 */ 889 firstOrDefault: function() { 890 if (arguments.length == 1) { 891 var defaultValue = arguments[0]; 892 } else if (arguments.length > 1) { 893 var predicate = arguments[0]; 894 var defaultValue = arguments[1]; 895 } 896 try { 897 if (arguments.length > 1) { 898 return this.first(predicate); 899 } else { 900 return this.first(); 901 } 902 } catch (e) { 903 return defaultValue; 904 } 905 }, 906 907 /** 908 * Groups the elements in the Enumerable. Note that this method 909 * resolves overloads based on function arity. Result selectors
910 * are expected to have two or more formal parameters while 911 * element- and key-selectors are expected to have just one. 912 * 913 * Overloads: 914 * groupBy(keySelector) 915 * groupBy(keySelector, comparer) 916 * groupBy(keySelector, elementSelector) 917 * groupBy(keySelector, resultSelector) 918 * groupBy(keySelector, resultSelector, comparer) 919 * groupBy(keySelector, elementSelector, comparer) 920 * groupBy(keySelector, elementSelector, resultSelector) 921 * groupBy(keySelector, elementSelector, resultSelector, 922 * comparer) 923 */ 924 groupBy: function() { 925 var keySelector = arguments[0]; 926 var elementSelector = identity; 927 var resultSelector = identity; 928 var comparer = EqualityComparer.getDefault(); 929 930 // Resolve overloads 931 // Todo: Dealing with overloads based on function arity is 932 // probably a bad idea. Maybe there is a better way? 933 if (arguments.length == 2) { 934 if (typeof arguments[1].equals == 'function') { 935 comparer = arguments[1]; 936 } else if (arguments[1].arity >= 2) { 937 resultSelector = arguments[1]; 938 } else { 939 elementSelector = arguments[1]; 940 } 941 } else if (arguments.length == 3) { 942 if (arguments[1].arity >= 2) { 943 resultSelector = arguments[1]; 944 comparer = arguments[2]; 945 } else { 946 elementSelector = arguments[1]; 947 if (typeof arguments[2].equals == 'function') { 948 comparer = arguments[2]; 949 } else { 950 resultSelector = arguments[2]; 951 } 952 } 953 } else if (arguments.length > 3) { 954 elementSelector = arguments[1]; 955 resultSelector = arguments[2]; 956 comparer = arguments[3]; 957 } 958 959 var _this = this; 960 961 var func = function() { 962 this.getEnumerator = function() { 963 return new function() { 964 var resultSet = null; 965 var index = -1; 966 967 function lazyInitialize() { 968 var itemEnumerator = _this.getEnumerator(); 969 var hash = new Hash(comparer); 970 971 while (itemEnumerator.moveNext()) { 972 var current = itemEnumerator.current(); 973 var key = keySelector(current); 974 if (hash.keyExists(key)) { 975 var array = hash.get(key); 976 array.push(elementSelector(current)); 977 } else { 978 hash.put(key, 979 [elementSelector(current)]); 980 } 981 } 982 resultSet = hash.toArray(); 983 } 984 985 this.moveNext = function() { 986 if (resultSet == null) { 987 lazyInitialize(); 988 } 989 ++index; 990 return index < resultSet.length; 991 }; 992 993 this.current = function() { 994 if (index < 0 || index >= resultSet.length) { 995 throw new InvalidOperationException(); 996 } 997 var current = resultSet[index]; 998 var elements = new Enumerable( 999 current.element); 1000 if (resultSelector != identity) { 1001 return resultSelector(current.key, 1002 elements); 1003 } else { 1004 var Grouping = function(key, elements) { 1005 this.getKey = function() { 1006 return key; 1007 }; 1008 1009 this.getEnumerator = function() { 1010 return elements.getEnumerator(); 1011 }; 1012 }; 1013 Grouping.prototype = Enumerable.prototype; 1014 return new Grouping(current.key, 1015 elements); 1016 } 1017 }; 1018 1019 this.reset = function() { 1020 index = -1; 1021 }; 1022 }; 1023 }; 1024 }; 1025 func.prototype = Enumerable.prototype; 1026 return new func(); 1027 }, 1028 1029 /** 1030 * Correlates the elements in two Enumerables based on their keys 1031 * and groups the results. 1032 * 1033 * Overloads: 1034 * groupJoin(inner, outerKeySelector, innerKeySelector, 1035 * resultSelector) 1036 * groupJoin(inner, outerKeySelector, innerKeySelector, 1037 * resultSelector, comparer) 1038 */ 1039 groupJoin: join(true), 1040 1041 /** 1042 * Returns a new Enumerable that is the result of the intersection 1043 * of two Enumerables. Optionally uses the specified comparer. 1044 * 1045 * Overloads: 1046 * intersect(second) 1047 * intersect(second, comparer) 1048 */ 1049 intersect: function(second, comparer) { 1050 if (arguments.length < 2) { 1051 comparer = EqualityComparer.getDefault(); 1052 } 1053 return this.distinct(comparer).where(function(item) { 1054 return second.contains(item, comparer); 1055 }); 1056 }, 1057 1058 /** 1059 * Correlates the elements in two Enumerables based on their 1060 * keys. 1061 * 1062 * Overloads: 1063 * join(inner, outerKeySelector, innerKeySelector, 1064 * resultSelector) 1065 * join(inner, outerKeySelector, innerKeySelector, 1066 * resultSelector, comparer) 1067 */ 1068 join: join(false), 1069 1070 /** 1071 * Returns the last element in the Enumerable or throws an 1072 * exception, if the Enumerable is empty. If a predicate function 1073 * is specified, the method returns the last element that 1074 * satisfies the given condition. If no element satisfies the 1075 * specified condition, an exception is thrown. 1076 * 1077 * Overloads:
1078 * last() 1079 * last(predicate) 1080 */ 1081 last: function(predicate) { 1082 var hasPredicate = typeof predicate == 'function'; 1083 1084 var last; 1085 var isEmpty = true; 1086 this.any(function(item) { 1087 if (!hasPredicate || predicate(item)) { 1088 last = item; 1089 isEmpty = false; 1090 } 1091 return false; 1092 }); 1093 1094 if (isEmpty) { 1095 throw new InvalidOperationException(); 1096 } 1097 return last; 1098 }, 1099 1100 /** 1101 * Same as last, except it returns a specified defaultValue if the 1102 * Enumerable is empty or not element satisfies the specified 1103 * condition. This method is different from its LINQ counterpart 1104 * in that it requires the default value to be specified 1105 * explicitly. 1106 * 1107 * Overloads: 1108 * lastOrDefault(defaultValue) 1109 * lastOrDefault(predicate, defaultValue) 1110 */ 1111 lastOrDefault: function() { 1112 if (arguments.length == 1) { 1113 var defaultValue = arguments[0]; 1114 } else if (arguments.length > 1) { 1115 var predicate = arguments[0]; 1116 var defaultValue = arguments[1]; 1117 } 1118 try { 1119 if (arguments.length > 1) { 1120 return this.last(predicate); 1121 } else { 1122 return this.last(); 1123 } 1124 } catch (e) { 1125 return defaultValue; 1126 } 1127 }, 1128 1129 /** 1130 * Returns the maximum value in the Enumerable. 1131 * 1132 * Overloads: 1133 * max() 1134 * max(selector) 1135 */ 1136 max: function(selector) { 1137 if (arguments.length == 0) { 1138 selector = identity; 1139 } 1140 var isFirst = true; 1141 return this.aggregate(function(running, current) { 1142 if (isFirst) { 1143 running = selector(running); 1144 isFirst = false; 1145 } 1146 return Math.max(running, selector(current)); 1147 }); 1148 }, 1149 1150 /** 1151 * Returns the minimum value in the Enumerable. 1152 * 1153 * Overloads: 1154 * min() 1155 * min(selector) 1156 */ 1157 min: function(selector) { 1158 if (arguments.length == 0) { 1159 selector = identity; 1160 } 1161 var isFirst = true; 1162 return this.aggregate(function(running, current) { 1163 if (isFirst) { 1164 running = selector(running); 1165 isFirst = false; 1166 } 1167 return Math.min(running, selector(current)); 1168 }); 1169 }, 1170 1171 /** 1172 * Sorts the elements in the Enumerable in ascending order. 1173 * 1174 * Overloads: 1175 * orderBy(keySelector) 1176 * orderBy(keySelector, comparer) 1177 */ 1178 orderBy: orderBy(ASCENDING), 1179 1180 /** 1181 * Sorts the elements in the Enumerable in descending order. 1182 * 1183 * Overloads: 1184 * orderByDescending(keySelector) 1185 * orderByDescending(keySelector, comparer) 1186 */ 1187 orderByDescending: orderBy(DESCENDING), 1188 1189 /** 1190 * Reverses the order of the elements in the Enumerable. 1191 */ 1192 reverse: function() { 1193 var _this = this; 1194 var func = function() { 1195 this.getEnumerator = function() { 1196 return new function() { 1197 var enumerator = null; 1198 1199 this.moveNext = function() { 1200 if (enumerator == null) { 1201 enumerator = new Enumerable( 1202 _this.toArray().reverse()). 1203 getEnumerator(); 1204 } 1205 return enumerator.moveNext(); 1206 }; 1207 1208 this.current = function() { 1209 if (enumerator == null) { 1210 throw new InvalidOperationException(); 1211 } 1212 return enumerator.current(); 1213 }; 1214 1215 this.reset = function() { 1216 if (enumerator != null) { 1217 enumerator.reset(); 1218 } 1219 }; 1220 }; 1221 } 1222 }; 1223 func.prototype = Enumerable.prototype; 1224 return new func(); 1225 }, 1226 1227 /**
1228 * Projects each of the elements in the Enumerable into a new 1229 * form. This differes from its LINQ counterpart in that the 1230 * selector function always receives the index of the current 1231 * element as its second parameter. 1232 */ 1233 select: function(selector) { 1234 var _this = this; 1235 var func = function() { 1236 this.getEnumerator = function() { 1237 return new function() { 1238 var itemEnumerator = _this.getEnumerator(); 1239 var index = -1; 1240 1241 this.moveNext = function() { 1242 ++index; 1243 return itemEnumerator.moveNext(); 1244 }; 1245 1246 this.current = function() { 1247 return selector(itemEnumerator.current(), 1248 index); 1249 }; 1250 1251 this.reset = function() { 1252 itemEnumerator.reset(); 1253 index = -1; 1254 }; 1255 }; 1256 }; 1257 }; 1258 func.prototype = Enumerable.prototype; 1259 return new func(); 1260 }, 1261 1262 /** 1263 * Projects each of the elements in the Enumerable into a new 1264 * Enumerable and then flattens the result into a single 1265 * Enumerable again. This differes from its LINQ counterpart in 1266 * that the collection selector function always receives the 1267 * index of the current element as its second parameter. 1268 * 1269 * Overloads: 1270 * selectMany(selector) 1271 * selectMany(collectionSelector, resultSelector) 1272 */ 1273 selectMany: function(collectionSelector, resultSelector) { 1274 var hasResultSelector = arguments.length > 1; 1275 var _this = this; 1276 var func = function() { 1277 this.getEnumerator = function() { 1278 return new function() { 1279 var itemEnumerator = _this.getEnumerator(); 1280 var item = null; 1281 var subItemEnumerator = null; 1282 var hasCurrent = false; 1283 var index = 0; 1284 1285 this.moveNext = function() { 1286 hasCurrent = false; 1287 var noMoveNext = true; 1288 while (true) { 1289 if (subItemEnumerator == null || 1290 !subItemEnumerator.moveNext()) { 1291 if (!itemEnumerator.moveNext()) { 1292 break; 1293 } 1294 item = itemEnumerator.current(); 1295 subItemEnumerator = 1296 collectionSelector(item, index++). 1297 getEnumerator(); 1298 noMoveNext = false; 1299 } 1300 if (noMoveNext || 1301 subItemEnumerator.moveNext()) { 1302 hasCurrent = true; 1303 break; 1304 } 1305 } 1306 return hasCurrent; 1307 }; 1308 1309 this.current = function() { 1310 if (!hasCurrent) { 1311 throw new InvalidOperationException(); 1312 } 1313 if (hasResultSelector) { 1314 return resultSelector(item, 1315 subItemEnumerator.current()); 1316 } else { 1317 return subItemEnumerator.current(); 1318 } 1319 }; 1320 1321 this.reset = function() { 1322 hasCurrent = false; 1323 subItemEnumerator = null; 1324 item = null; 1325 itemEnumerator.reset(); 1326 index = 0; 1327 }; 1328 }; 1329 }; 1330 }; 1331 func.prototype = Enumerable.prototype; 1332 return new func(); 1333 }, 1334 1335 /** 1336 * Returns true if the Enumerable is identical to another 1337 * Enumerable by comparing the elements using an optional 1338 * comparer. 1339 * 1340 * Overloads: 1341 * sequenceEqual(second) 1342 * sequenceEqual(second, comparer) 1343 */ 1344 sequenceEqual: function(second, comparer) { 1345 if (this == second) { 1346 return true; 1347 } 1348 if (arguments.length < 2) { 1349 comparer = EqualityComparer.getDefault(); 1350 } 1351 var firstEnumerator = this.getEnumerator(); 1352 var secondEnumerator = second.getEnumerator(); 1353 1354 while (firstEnumerator.moveNext() && 1355 secondEnumerator.moveNext()) { 1356 if (!comparer.equals(firstEnumerator.current(), 1357 secondEnumerator.current())) { 1358 return false; 1359 } 1360 } 1361 return true; 1362 }, 1363 1364 /** 1365 * Returns the only element in the Enumerable or throws an 1366 * exception either if the Enumerable is empty or if there is more 1367 * than one element in the Enumerable. Optionally tests the 1368 * returned element against a predicate function and throws an 1369 * exception if the element does not satisfy its condition. 1370 * 1371 * Overloads: 1372 * single() 1373 * single(predicate) 1374 */ 1375 single: function(predicate) { 1376 var hasPredicate = arguments.length > 0;
1377 var enumerator = this.getEnumerator(); 1378 if (!enumerator.moveNext()) { 1379 throw new InvalidOperationException(); 1380 } 1381 var current = enumerator.current(); 1382 if (enumerator.moveNext() || (hasPredicate && 1383 !predicate(current))) { 1384 throw new InvalidOperationException(); 1385 } 1386 return current; 1387 }, 1388 1389 /** 1390 * Same as single, except that a specified default value is 1391 * returned if the Enumerable is empty, contains more than one 1392 * element or if the element does not satisfy the specified 1393 * condition. This method differs from its LINQ counterpart in 1394 * that it requires the default value to be specified explicitly. 1395 * 1396 * Overloads: 1397 * singleOrDefault(defaultValue) 1398 * singleOrDefault(predicate, defaultValue) 1399 */ 1400 singleOrDefault: function() { 1401 if (arguments.length == 1) { 1402 var defaultValue = arguments[0]; 1403 } else if (arguments.length > 1) { 1404 var predicate = arguments[0]; 1405 var defaultValue = arguments[1]; 1406 } 1407 try { 1408 if (arguments.length > 1) { 1409 return this.single(predicate); 1410 } else { 1411 return this.single(); 1412 } 1413 } catch (e) { 1414 return defaultValue; 1415 } 1416 }, 1417 1418 /** 1419 * Returns a new Enumerable that is the result of skipping the 1420 * first "count" elements of the Enumerable. 1421 */ 1422 skip: function(count) { 1423 if (count == 0) { 1424 return this; 1425 } 1426 return this.skipWhile(function(item, index) { 1427 return index < count; 1428 }); 1429 }, 1430 1431 /** 1432 * Returns a new Enumerable that is the result of skipping all 1433 * elements from the beginning of the Enumerable that satisfy a 1434 * specified condition. 1435 */ 1436 skipWhile: function(predicate) { 1437 var _this = this; 1438 var func = function() { 1439 this.getEnumerator = function() { 1440 return new function() { 1441 var enumerator = _this.getEnumerator(); 1442 var isInitialized = false; 1443 var index = 0; 1444 1445 this.moveNext = function() { 1446 if (!isInitialized) { 1447 var canMoveNext = false; 1448 while ((canMoveNext = 1449 enumerator.moveNext()) && 1450 predicate(enumerator.current(), 1451 index++)) { } 1452 isInitialized = true; 1453 return canMoveNext; 1454 } else { 1455 return enumerator.moveNext(); 1456 } 1457 }; 1458 1459 this.current = function() { 1460 return enumerator.current(); 1461 }; 1462 1463 this.reset = function() { 1464 enumerator.reset(); 1465 isInitialized = false; 1466 index = 0; 1467 }; 1468 }; 1469 }; 1470 }; 1471 func.prototype = Enumerable.prototype; 1472 return new func(); 1473 }, 1474 1475 /** 1476 * Returns the sum of the elements in the Enumerable, optionally 1477 * using a specified selector. 1478 * 1479 * Overloads: 1480 * sum() 1481 * sum(selector) 1482 */ 1483 sum: function(selector) { 1484 if (arguments.length == 0) { 1485 selector = identity; 1486 } 1487 return this.aggregate(0, function(running, current) { 1488 return running + selector(current); 1489 }); 1490 }, 1491 1492 /** 1493 * Returns a new Enumerable that is the result of extracting the 1494 * first "count" elements from the Enumerable. 1495 */ 1496 take: function(count) { 1497 if (count == 0) { 1498 return Enumerable.empty(); 1499 } 1500 return this.takeWhile(function(item, index) { 1501 return index < count; 1502 }); 1503 }, 1504 1505 /** 1506 * Returns a new Enumerable that is the result of extracting all 1507 * elements from the beginning of the Enumerable that satisfy the 1508 * specified condition. 1509 */ 1510 takeWhile: function(predicate) { 1511 var _this = this; 1512 var func = function() { 1513 this.getEnumerator = function() { 1514 return new function() { 1515 var enumerator = _this.getEnumerator(); 1516 var isOperational = true; 1517 var index = 0; 1518 1519 this.moveNext = function() { 1520 if (isOperational) { 1521 isOperational = enumerator.moveNext() && 1522 predicate(enumerator.current(), 1523 index++); 1524 } 1525 return isOperational; 1526 }; 1527 1528 this.current = function() { 1529 if (!isOperational) { 1530 throw new InvalidOperationException(); 1531 } 1532 return enumerator.current(); 1533 }; 1534 1535 this.reset = function() { 1536 enumerator.reset(); 1537 isOperational = true; 1538 index = 0; 1539 }; 1540 }; 1541 }; 1542 }; 1543 func.prototype = Enumerable.prototype; 1544 return new func(); 1545 }, 1546 1547 /** 1548 * Returns an array containg all the elements in Enumerable. 1549 */ 1550 toArray: function() { 1551 var enumerator = this.getEnumerator(); 1552 var array = []; 1553 while (enumerator.moveNext()) { 1554 array.push(enumerator.current()); 1555 } 1556 return array; 1557 }, 1558 1559 /** 1560 * Returns a new Enumerable that is the union of two Enumerables. 1561 * Optionally uses the specified comparer. 1562 * 1563 * Overloads: 1564 * union(second) 1565 * union(second, comparer) 1566 */ 1567 union: function(second, comparer) { 1568 if (arguments.length < 2) { 1569 comparer = EqualityComparer.getDefault(); 1570 } 1571 return this.concat(second).distinct(comparer); 1572 }, 1573 1574 /** 1575 * Returns a new Enumerable that contains only the elements of the 1576 * Enumerable that satisfy the specified condition. 1577 */ 1578 where: function(predicate) { 1579 var _this = this; 1580 var func = function() { 1581 this.getEnumerator = function() { 1582 return new function() { 1583 var itemEnumerator = _this.getEnumerator(); 1584 var current; 1585 var hasCurrent = false; 1586 this.moveNext = function() { 1587 hasCurrent = false; 1588 while (itemEnumerator.moveNext()) { 1589 current = itemEnumerator.current(); 1590 if (predicate(current)) { 1591 hasCurrent = true; 1592 break; 1593 } 1594 } 1595 return hasCurrent; 1596 }; 1597 1598 this.current = function() { 1599 if (!hasCurrent) { 1600 throw new InvalidOperationException(); 1601 } 1602 return current; 1603 }; 1604 1605 this.reset = function() { 1606 itemEnumerator.reset(); 1607 hasCurrent = false; 1608 }; 1609 }; 1610 }; 1611 }; 1612 func.prototype = Enumerable.prototype; 1613 return new func(); 1614 } 1615 }; 1616 })(); 1617 1618 this.InvalidOperationException = InvalidOperationException; 1619 this.ArgumentOutOfRangeException = ArgumentOutOfRangeException; 1620 this.EqualityComparer = EqualityComparer; 1621 this.Comparer = Comparer; 1622 this.Enumerable = Enumerable; 1623}).call(jsinq);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.