1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 // AjxVector class
 25 
 26 /**
 27  * Creates a vector.
 28  * @class
 29  * This class represents a vector.
 30  * 
 31  */
 32 AjxVector = function(array) {
 33 	this._array = array || [];
 34 };
 35 
 36 AjxVector.prototype.isAjxVector = true;
 37 
 38 /**
 39  * Returns a string representation of the object.
 40  * 
 41  * @param	{string}	sep		the seperator
 42  * @param	{boolean}	compress	if <code>true</code>, compress
 43  * 
 44  * @return	{string}	a string representation of the object
 45  */
 46 AjxVector.prototype.toString =
 47 function(sep, compress) {
 48 	if (compress !== true)
 49 		return this._array.join(sep);
 50 
 51 	var a = new Array();
 52 	for (var i = 0; i < this._array.length; i++) {
 53 		var x = this._array[i];
 54 		if  (x != undefined && x != null && x != "")
 55 			a.push(x);
 56 	}
 57 	return a.join(sep);
 58 };
 59 
 60 /**
 61  * Creates a vector from a given array.
 62  * 
 63  * @param	{array}	list		an array
 64  * @return	{AjxVector}		the vector
 65  */
 66 AjxVector.fromArray =
 67 function(list) {
 68 	var vec = new AjxVector();
 69 	if (AjxUtil.isArray1(list)) {
 70 		vec._array = list;
 71 	}
 72 	return vec;
 73 };
 74 
 75 /**
 76  * Gets the size of the vector.
 77  * 
 78  * @return	{number}	the size
 79  */
 80 AjxVector.prototype.size =
 81 function() {
 82 	return this._array.length;
 83 };
 84 
 85 /**
 86  * Adds a object to the vector.
 87  * 
 88  * @param	{Object}	obj		the object
 89  * @param	{number}		index	the index where to add
 90  * @param	{boolean}	noDuplicates	if <code>true</code>, confirm the object is not in vector before adding
 91  */
 92 AjxVector.prototype.add =
 93 function(obj, index, noDuplicates) {
 94 	// if no duplicates, search for the obj in list and return if found.
 95 	if (noDuplicates && this.contains(obj)) {
 96 		return;
 97 	}
 98 
 99 	AjxUtil.arrayAdd(this._array, obj, index);
100 };
101 
102 /**
103  * Adds the given array.
104  * 
105  * @param	{array}		list		an array
106  */
107 AjxVector.prototype.addList =
108 function(list) {
109 	if (!list) return;
110 
111 	if (list.length) {// array
112 		this._array = this._array.concat(list);
113 	} else if (list.size && list.size()) {// AjxVector
114 		// in new window, IE seems to lose its rtti :(
115 		if (AjxEnv.isIE && (!(list._array instanceof Array))) {
116 			var newList = [];
117 			for (var i = 0; i < list._array.length; i++) {
118 				newList.push(list._array[i]);
119 			}
120 			list._array = newList;
121 		}
122 
123 		this._array = this._array.concat(list._array);
124 	}
125 };
126 
127 /**
128  * Removes the object.
129  * 
130  * @param	{Object}	obj		the object to remove
131  * @return	{boolean}	<code>true</code> if the object is removed
132  */
133 AjxVector.prototype.remove =
134 function(obj) {
135 	return AjxUtil.arrayRemove(this._array, obj);
136 };
137 
138 /**
139  * Removes the object at the given index.
140  * 
141  * @param	{number}	index		the index
142  * @return	{Object}	the object at the index or <code>null</code> if no object at index
143  */
144 AjxVector.prototype.removeAt =
145 function(index) {
146 	if (index >= this._array.length || index < 0)
147 		return null;
148 
149 	var delArr = this._array.splice(index, 1);
150 	var ret = null;
151 	if (delArr) {
152 		ret = delArr[0];
153 	}
154 	return ret;
155 };
156 
157 /**
158  * Removes all objects from vector.
159  * 
160  */
161 AjxVector.prototype.removeAll =
162 function() {
163 	// Actually blow away the array items so that garbage
164 	// collection can take place (XXX: does this really force GC?)
165 	for (var i = 0; i < this._array.length; i++)
166 		this._array[i] = null;
167 	this._array.length = 0;
168 };
169 
170 /**
171  * Removes the last object in the vector.
172  * 
173  */
174 AjxVector.prototype.removeLast =
175 function() {
176 	return this._array.length > 0 ? this._array.pop() : null;
177 };
178 
179 /**
180  * Reverses the order of the objects in the vector.
181  * 
182  */
183 AjxVector.prototype.reverse =
184 function() {
185 	this._array.reverse();
186 };
187 
188 /**
189  * Replaces the object at a given index.
190  * 
191  * @param	{number}	index		the index
192  * @param	{Object}	newObj	the new object
193  * @return	{Object}	the old object
194  */
195 AjxVector.prototype.replace =
196 function(index, newObj) {
197 	var oldObj = this._array[index];
198 	this._array[index] = newObj;
199 	return oldObj;
200 };
201 
202 /**
203  * Replaces an object.
204  * 
205  * @param	{Object}	obj		the object to replace
206  * @param	{Object}	newObj	the new object
207  * @return	{Object}	the replaced object or <code>null</code> if not replaced
208  */
209 AjxVector.prototype.replaceObject =
210 function(obj, newObj) {
211 	for (var i = 0; i < this._array.length; i++) {
212 		if (this._array[i] == obj) {
213 			this._array[i] = newObj;
214 			return obj;
215 		}
216 	}
217 	return null;
218 };
219 
220 /**
221  * Returns the index of the obj given w/in vector
222  *
223  * @param {Object}	    obj			the object being looked for
224  * @param {function}	func	    (optional) a function for transforming objects
225  *
226  * @return	{number}	the index or -1 if not found
227  */
228 AjxVector.prototype.indexOf = function(obj, func) {
229 
230 	if (obj == null) {
231 		return -1;
232 	}
233     obj = func ? func.call(obj) : obj;
234 
235 	for (var i = 0; i < this._array.length; i++) {
236         var member = this._array[i],
237             test = func ? func.call(member) : member;
238 		if (test === obj) {
239 			return i;
240 		}
241 	}
242 	return -1;
243 };
244 
245 AjxVector.prototype.indexOfLike = AjxVector.prototype.indexOf;
246 
247 /**
248  * Returns the last index of the obj given w/in vector
249  *
250  * @param {Object}	    obj			the object being looked for
251  * @param {function}	func	    (optional) a function for transforming objects
252  *
253  * @return	{number}	the index or -1 if not found
254  */
255 AjxVector.prototype.lastIndexOf = function(obj, func) {
256 
257 	if (obj == null) {
258 		return -1;
259 	}
260     obj = func ? func.call(obj) : obj;
261 
262 	for (var i = this._array.length - 1; i >= 0; i--) {
263         var member = this._array[i],
264             test = func ? func.call(member) : member;
265 		if (member === obj) {
266 			return i;
267 		}
268 	}
269 	return -1;
270 };
271 
272 AjxVector.prototype.lastIndexOfLike = AjxVector.prototype.lastIndexOf;
273 
274 /**
275  * Returns the last index of the obj given w/in vector
276  *
277  * @param {Object}	obj			the object being looked for
278  * @param {function}	keyFunc	a function for transforming objects
279  * @return	{number}	the index or -1 if not found
280  */
281 AjxVector.prototype.lastIndexOfLike =
282 function(obj, keyFunc) {
283 	var value = keyFunc.call(obj);
284 
285 	for (var i = this._array.length - 1; i >= 0; i--) {
286 		var test = keyFunc.call(this._array[i]);
287 		if (test == value)
288 			return i;
289 	}
290 	return -1;
291 };
292 
293 /**
294  * Clones the vector.
295  * 
296  * @return	{AjxVector}	the new vector
297  */
298 AjxVector.prototype.clone =
299 function() {
300 	var vec = new AjxVector();
301 	vec.addList(this);
302 	return vec;
303 };
304 
305 /**
306  * Checks if the vector contains an object.
307  * 
308  * @param	{Object}	obj		the object
309  * @return	{boolean}	<code>true</code> if the object is found
310  */
311 AjxVector.prototype.contains =
312 function(obj) {
313 	return AjxUtil.arrayContains(this._array, obj);
314 };
315 
316 
317 /**
318  * Returns true if the vector contains the given object, using the given
319  * function to compare objects. The comparison function should return a
320  * type for which the equality test (==) is meaningful, such as a string
321  * or a base type.
322  *
323  * @param {Object}	obj			the object being looked for
324  * @param {function}	keyFunc	a function for transforming objects
325  * @return	{boolean}	<code>true</code> if the object is found
326  */
327 AjxVector.prototype.containsLike =
328 function(obj, keyFunc) {
329 	var value = keyFunc.call(obj);
330 	for (var i = 0; i < this._array.length; i++) {
331 		var test = keyFunc.call(this._array[i]);
332 		if (test == value)
333 			return true;
334 	}
335 	return false;
336 };
337 
338 /**
339  * Gets the object at a given index.
340  * 
341  * @param	{number}	index		the index
342  * @return	{Object}	the object or <code>null</code> if not found
343  */
344 AjxVector.prototype.get =
345 function(index) {
346 	return index >= this._array.length || index < 0
347 		? null : this._array[index];
348 };
349 
350 /**
351  * Gets an array of the vector.
352  * 
353  * @return	{array}	an array
354  */
355 AjxVector.prototype.getArray =
356 function() {
357 	return this._array;
358 };
359 
360 /**
361  * Gets the last object in the vector.
362  * 
363  * @return	{Object}	the object or <code>null</code> if vector is empty
364  */
365 AjxVector.prototype.getLast =
366 function() {
367 	return this._array.length == 0
368 		? null : this._array[this._array.length-1];
369 };
370 
371 /**
372  * Gets the next object in the vector after a given object.
373  * 
374  * @param	{Object}	obj		the object
375  * @return	{Object}	the object or <code>null</code> if object not found
376  */
377 AjxVector.prototype.getNext =
378 function(obj) {
379 	var idx = this.indexOf(obj);
380 	if (idx == -1)
381 		return null;
382 	return this.get(++idx);
383 };
384 
385 /**
386  * Gets the previous object in the vector before a given object.
387  * 
388  * @param	{Object}	obj		the object
389  * @return	{Object}	the object or <code>null</code> if object not found
390  */
391 AjxVector.prototype.getPrev =
392 function(obj) {
393 	var idx = this.indexOf(obj);
394 	if (idx == -1)
395 		return null;
396 	return this.get(--idx);
397 };
398 
399 /**
400  * Sorts the vector.
401  * 
402  * @param	{function}	sortFunc		the function
403  */
404 AjxVector.prototype.sort =
405 function(sortFunc) {
406 	if (!sortFunc) {
407 		sortFunc = AjxVector._defaultArrayComparator;
408 	}
409 	this._array.sort(sortFunc);
410 };
411 
412 /**
413  * Performs a binary search.
414  * 
415  * @param	{Object}	valueToFind		the value
416  * @param	{function}	sortFunc		the sort function
417  * @return	{number}	the index
418  */
419 AjxVector.prototype.binarySearch =
420 function(valueToFind, sortFunc) {
421 	if (!sortFunc) {
422 		sortFunc = AjxVector._defaultArrayComparator;
423 	}
424 
425 	var l = 0;
426 	var arr = this._array;
427 	var u = arr.length - 1;
428 
429 	while(true) {
430 		if (u < l) {
431 			return -1;
432 		}
433 
434 		var i = Math.floor((l + u)/ 2);
435 		var comparisonResult = sortFunc(valueToFind, arr[i]);
436 
437 		if (comparisonResult < 0) {
438 			u = i - 1;
439 		} else if (comparisonResult > 0) {
440 			l = i + 1;
441 		} else {
442 			return i;
443 		}
444 	}
445 };
446 
447 AjxVector.prototype.merge =
448 function(offset, list) {
449 
450 	if (offset < 0)
451 		return;
452 
453 	var rawList = list instanceof AjxVector ? list.getArray() : list;
454 
455 	var limit = this._array.length < (offset+rawList.length)
456 		? this._array.length
457 		: offset+rawList.length;
458 
459 	if (offset < this._array.length) {
460 		// replace any overlapping items in vector
461 		var count = 0;
462 		for (var i=offset; i<limit; i++)
463 			this._array[i] = rawList[count++];
464 
465 		// and append the rest
466 		if (count < rawList.length)
467 			this._array = this._array.concat(rawList.slice(count));
468 	} else {
469 		// otherwise, just append the raw list to the end
470 		this._array = this._array.concat(rawList);
471 	}
472 };
473 
474 
475 // Static methods
476 
477 AjxVector._defaultArrayComparator =
478 function(a, b) {
479 	return a < b ? -1 : (a > b ? 1 : 0);
480 };
481 
482 // Apply function f for each element of the array.  Optionally call it
483 // in the context of obj object.  If "f" is a string, then for each
484 // non-null array element call its "f" member function.
485 AjxVector.prototype.foreach = function(f, obj) {
486 	var l = this.size(), i = 0, el;
487 	if (typeof f == "function") {
488 		while (--l >= 0)
489 			f.call(obj, this.get(i), i++);
490 	} else {
491 		while (--l >= 0) {
492 			el = this.get(i++);
493 			if (el != null)
494 				el[f].call(el); // assuming function
495 		}
496 	}
497 };
498 
499 /**
500  * Return a new AjxVector which contains the results of calling f
501  * (optionally in the context obj) for each element of this array.
502  * <ul>
503  * <li>If "f" is a string, then for each element el:
504  * <ul>
505  * <li>if el[f] is a function, call el[f] and push the result in the returned array.</li>
506  * <li>otherwise push el[f]</li>
507  * </ul>
508  * </li>
509  * </ul>
510  * 
511  * @param	{function}	f 	the function
512  * @param	{Object}	obj		the obj context
513  * @return	{AjxVector}		the resulting vector
514  */
515 AjxVector.prototype.map = function(f, obj) {
516 	var a = [], i = this.size(), el;
517 	if (typeof f == "function") {
518 		while (--i >= 0)
519 			a[i] = f.call(obj, this.get(i), i);
520 	} else if (f instanceof AjxCallback) {
521 		while (--i >= 0)
522 			a[i] = f.run(this.get(i), i);
523 	} else {
524 		while (--i >= 0) {
525 			el = this.get(i);
526 			if (el != null) {
527 				if (typeof el[f] == "function")
528 					a.unshift(el[f].call(el));
529 				else
530 					a.unshift(el[f]);
531 			}
532 		}
533 	}
534 	return AjxVector.fromArray(a);
535 };
536 
537 /**
538  * Returns an AjxVector with all the members of the given array for which the
539  * filtering function returns true.
540  *
541  * @param {Function}    func        filtering function
542  * @param {Object}      context     scope for filtering function
543  *
544  * @returns {Array} array of members for which the filtering function returns true
545  */
546 AjxVector.prototype.filter = function(func, context) {
547 	return AjxVector.fromArray(AjxUtil.filter(this._array, func, context));
548 };
549 
550 /**
551  * Joins the vector.
552  * 
553  * @param	{string}	sep		the string separator
554  * @return	{string}	a string representation of the vector
555  */
556 AjxVector.prototype.join = function(sep) {
557 	return this._array.join(sep);
558 };
559 
560 /**
561  * Return true if the given function returns true for a member of this vector,
562  * otherwise false.
563  *
564  * @param	{function}	f 	the function
565  * @param	{Object}	obj		the obj context
566 */
567 AjxVector.prototype.some =
568 function(f, obj) {
569 	return this._array.some(f, obj);
570 };
571 
572 
573 /**
574  * Return a new AjxVector containing the elements from this vector
575  * except those for which f(el) returns true.  Otherwise said,
576  * "SUBtracts" from this vector those elements for which f(el) returns true.
577  *
578  * @param	{function}	f 	the function
579  * @param	{Object}	obj		the obj context
580  * @return	{AjxVector}		the resulting vector
581  */
582 AjxVector.prototype.sub = function(f, obj) {
583 	var a = [], l = this.size(), i = 0, el;
584 	while (--l >= 0) {
585 		el = this.get(i++);
586 		if (!f.call(obj, el, i))
587 			a.push(el);
588 	}
589 	return AjxVector.fromArray(a);
590 };
591 
592 AjxVector.prototype.slice =
593 function(start, end) {
594 	return AjxVector.fromArray(this._array.slice(start, end));
595 };
596