mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-29 00:30:44 +00:00
b696e1e84b
Was cloning the original set, then adding to it - resulting in always containing the whole set in any match. Also using test instead of exec since exec returns a string result, which under strange circumstances could return a falsy result, despite the RegExp finding a match. Change-Id: I09a7cb264521d58f02d6ff2547edad9a740b23b2
355 lines
10 KiB
JavaScript
355 lines
10 KiB
JavaScript
/**
|
|
* VisualEditor OrderedHashSet class.
|
|
*
|
|
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Ordered set of objects. Objects are treated as equal if their hashes are equal. This means that
|
|
* you can't add two objects with the same hash, and it also means that you can search for one
|
|
* object and get a different one back that has the same hash.
|
|
*
|
|
* The second parameter to this constructor can be an array of objects, or an existing
|
|
* ve.OrderedHashSet object. In both cases, the new Set will be a shallow copy: object references
|
|
* to the individual objects will be maintained, but references to the original array or
|
|
* ve.OrderedHashSet will not be.
|
|
*
|
|
* @class
|
|
* @abstract
|
|
* @constructor
|
|
* @param {Function} hash Hash function
|
|
* @param {Array|ve.OrderedHashSet} [items] Items to populate this set with
|
|
*/
|
|
ve.OrderedHashSet = function ( hash, items ) {
|
|
var i;
|
|
this.hash = hash;
|
|
// Array of object references
|
|
this.arr = [];
|
|
// Object mapping object hashes to object references
|
|
this.map = {};
|
|
|
|
// HACK HACK HACK
|
|
// We should fix our inheritance so instanceof works
|
|
if ( items instanceof ve.OrderedHashSet || items instanceof ve.AnnotationSet ) {
|
|
this.map = ve.extendObject( {}, items.map );
|
|
this.arr = items.arr.slice( 0 );
|
|
} else if ( ve.isArray( items ) && items.length > 0 ) {
|
|
this.arr = items.slice( 0 );
|
|
for ( i = 0; i < this.arr.length; i++ ) {
|
|
this.map[this.hash( this.arr[i] )] = this.arr[i];
|
|
}
|
|
}
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Create a clone of this set, with the same hash function and the same contents.
|
|
* @returns {ve.OrderedHashSet}
|
|
*/
|
|
ve.OrderedHashSet.prototype.clone = function () {
|
|
return new ve.OrderedHashSet( this.hash, this );
|
|
};
|
|
|
|
/**
|
|
* Get a value or all values from the set.
|
|
*
|
|
* set.get( 5 ) returns the object at index 5, set.get() returns an array with all objects in
|
|
* the entire set.
|
|
*
|
|
* @param {Number} [index] If set, only get the element at this index
|
|
* @returns {Array|Object|undefined} The object at index, or an array of all objects in the set
|
|
*/
|
|
ve.OrderedHashSet.prototype.get = function ( index ) {
|
|
if ( index !== undefined ) {
|
|
return this.arr[index];
|
|
} else {
|
|
return this.arr.slice( 0 );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @returns {Number} The number of objects in the set
|
|
*/
|
|
ve.OrderedHashSet.prototype.getLength = function () {
|
|
return this.arr.length;
|
|
};
|
|
|
|
/**
|
|
* @returns {Boolean} True if the set is empty, false otherwise
|
|
*/
|
|
ve.OrderedHashSet.prototype.isEmpty = function () {
|
|
return this.arr.length === 0;
|
|
};
|
|
|
|
/**
|
|
* Check whether a given value occurs in the set. Values are compared by hash.
|
|
*
|
|
* @returns {Boolean} True if there is an object in the set with the same hash as value, false otherwise
|
|
*/
|
|
ve.OrderedHashSet.prototype.contains = function ( value ) {
|
|
return this.hash( value ) in this.map;
|
|
};
|
|
|
|
/**
|
|
* Check whether this set contains any of the values in another set.
|
|
*
|
|
* @param {ve.OrderedHashSet} set Set to compare this set with
|
|
* @returns {Boolean} True if there is at least one value in set that is also in this set, false otherwise
|
|
*/
|
|
ve.OrderedHashSet.prototype.containsAnyOf = function ( set ) {
|
|
var key;
|
|
for ( key in set.map ) {
|
|
if ( key in this.map ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Check whether this set contains all of the values in another set.
|
|
*
|
|
* @param {ve.OrderedHashSet} set Set to compare this set with
|
|
* @returns {Boolean} True if all values in set are also in this set, false otherwise
|
|
*/
|
|
ve.OrderedHashSet.prototype.containsAllOf = function ( set ) {
|
|
var key;
|
|
for ( key in set.map ) {
|
|
if ( !( key in this.map ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Get the index of a given value in this set.
|
|
*
|
|
* @param {Object} value Value to search for
|
|
* @returns {Number} Index of value in this set, or -1 if value is not in this set.
|
|
*/
|
|
ve.OrderedHashSet.prototype.indexOf = function ( value ) {
|
|
var hash = this.hash( value );
|
|
if ( hash in this.map ) {
|
|
return ve.indexOf( this.map[hash], this.arr );
|
|
} else {
|
|
return -1;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Filter this set by a given property. This returns a new set with all values in this set for
|
|
* which value.property matches filter (if filter is a RegExp) or is equal to filter,
|
|
*
|
|
* @param {String} property Property to check
|
|
* @param {Mixed|RegExp} filter Regular expression or value to filter for
|
|
* @param {Boolean} [returnBool] For internal use only
|
|
* @returns {ve.OrderedHashSet} New set containing only the matching values
|
|
*/
|
|
ve.OrderedHashSet.prototype.filter = function ( property, filter, returnBool ) {
|
|
var i, result;
|
|
if ( !returnBool ) {
|
|
// TODO: Consider alternative ways to instantiate a new set of the same type as the subclass
|
|
result = this.clone();
|
|
// TODO: Should we be returning this on all methods that modify the original? Might help
|
|
// with chainability, but perhaps it's also confusing because most chainable methods return
|
|
// a new hash set.
|
|
result.removeAll();
|
|
}
|
|
for ( i = 0; i < this.arr.length; i++ ) {
|
|
if (
|
|
( filter instanceof RegExp && filter.test( this.arr[i][property] ) ) ||
|
|
( typeof filter === 'string' && this.arr[i][property] === filter )
|
|
) {
|
|
if ( returnBool ) {
|
|
return true;
|
|
} else {
|
|
result.push( this.arr[i] );
|
|
}
|
|
}
|
|
}
|
|
return returnBool ? false : result;
|
|
};
|
|
|
|
/**
|
|
* Check if this set contains at least one value where a given property matches a given filter.
|
|
*
|
|
* This is equivalent to (but more efficient than) !this.filter( .. ).isEmpty()
|
|
* @see ve.OrderedHashSet.prototype.filter
|
|
*
|
|
* @param {String} property
|
|
* @param {Mixed|RegExp} filter
|
|
* @returns {Boolean} True if at least one value matches, false otherwise
|
|
*/
|
|
ve.OrderedHashSet.prototype.containsMatching = function ( property, filter ) {
|
|
return this.filter( property, filter, true );
|
|
};
|
|
|
|
/**
|
|
* Add a value to this set. If the value is already present in this set, nothing happens.
|
|
*
|
|
* The value will be inserted before the value that is currently at the given index. If index is
|
|
* negative, it will be counted from the end (i.e. index -1 is the last item, -2 the second-to-last,
|
|
* etc.). If index is out of bounds, the value will be added to the end of the set.
|
|
*
|
|
* @param {Object} value Value to add
|
|
* @param {Number} index Index to add the value at
|
|
*/
|
|
ve.OrderedHashSet.prototype.add = function ( value, index ) {
|
|
var hash;
|
|
if ( index < 0 ) {
|
|
index = this.arr.length + index;
|
|
}
|
|
if ( index >= this.arr.length ) {
|
|
this.push( value );
|
|
return;
|
|
}
|
|
|
|
hash = this.hash( value );
|
|
if ( !( hash in this.map ) ) {
|
|
this.arr.splice( index, 0, value );
|
|
this.map[hash] = value;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add all values in the given set to the end of this set. Values from the other set that are
|
|
* already in this set will not be added again.
|
|
* @param {ve.OrderedHashSet} set Set to add to this set
|
|
*/
|
|
ve.OrderedHashSet.prototype.addSet = function ( set ) {
|
|
var i;
|
|
for ( i = 0; i < set.arr.length; i++ ) {
|
|
this.push( set.arr[i] );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add a value at the end of this set. If the value is already present in this set, nothing happens.
|
|
* @param {Object} value Value to add
|
|
*/
|
|
ve.OrderedHashSet.prototype.push = function ( value ) {
|
|
var hash = this.hash( value );
|
|
if ( !( hash in this.map ) ) {
|
|
this.arr.push( value );
|
|
this.map[hash] = value;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove the value at a given index
|
|
* @param {Number} index Index to remove item at. If negative, this counts from the end, see add()
|
|
* @throws {Error} 'Index out of bounds'
|
|
*/
|
|
ve.OrderedHashSet.prototype.removeAt = function ( index ) {
|
|
if ( index < 0 ) {
|
|
index = this.arr.length + index;
|
|
}
|
|
if ( index >= this.arr.length ) {
|
|
throw new Error( 'Index out of bounds' );
|
|
}
|
|
delete this.map[this.hash( this.arr[index] )];
|
|
this.arr.splice( index, 1 );
|
|
};
|
|
|
|
/**
|
|
* Remove a given value from the set. If the value isn't in the set, nothing happens.
|
|
* @param {Object} value Value to remove
|
|
*/
|
|
ve.OrderedHashSet.prototype.remove = function ( value ) {
|
|
var index, hash = this.hash( value );
|
|
if ( hash in this.map ) {
|
|
index = ve.indexOf( this.map[hash], this.arr );
|
|
delete this.map[hash];
|
|
this.arr.splice( index, 1 );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove all values.
|
|
*/
|
|
ve.OrderedHashSet.prototype.removeAll = function () {
|
|
var i;
|
|
for ( i = 0; i < this.arr.length; i++ ) {
|
|
this.remove( this.arr[i] );
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Remove all values in a given set from this set. Values that aren't in this set are ignored.
|
|
* @param {ve.OrderedHashSet} set Set to remove from this set
|
|
*/
|
|
ve.OrderedHashSet.prototype.removeSet = function ( set ) {
|
|
var i;
|
|
for ( i = 0; i < set.arr.length; i++ ) {
|
|
this.remove( set.arr[i] );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove all values that are not also in a given other set from this set.
|
|
* @param {ve.OrderedHashSet} set Set to intersect with this set
|
|
*/
|
|
ve.OrderedHashSet.prototype.removeNotInSet = function ( set ) {
|
|
var i;
|
|
for ( i = this.arr.length - 1; i >= 0; i-- ) {
|
|
if ( !set.contains( this.arr[i] ) ) {
|
|
this.removeAt( i );
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reverse this set.
|
|
*
|
|
* This returns a copy, the original set is not modified.
|
|
*
|
|
* @returns {ve.OrderedHashSet} Copy of this set with the order reversed.
|
|
*/
|
|
ve.OrderedHashSet.prototype.reversed = function () {
|
|
var newSet = this.clone();
|
|
newSet.arr.reverse();
|
|
return newSet;
|
|
};
|
|
|
|
/**
|
|
* Merge another set into this set.
|
|
*
|
|
* This returns a copy, the original set is not modified.
|
|
*
|
|
* @param {ve.OrderedHashSet} set Other set
|
|
* @returns {ve.OrderedHashSet} Set containing all values in this set as well as all values in set
|
|
*/
|
|
ve.OrderedHashSet.prototype.mergeWith = function ( set ) {
|
|
var newSet = this.clone();
|
|
newSet.addSet( set );
|
|
return newSet;
|
|
};
|
|
|
|
/**
|
|
* Get the difference between this set and another set.
|
|
*
|
|
* @param {ve.OrderedHashSet} set Other set
|
|
* @returns {ve.OrderedHashSet} New set containing all values that are in this set but not in set
|
|
*/
|
|
ve.OrderedHashSet.prototype.diffWith = function ( set ) {
|
|
var newSet = this.clone();
|
|
newSet.removeSet( set );
|
|
return newSet;
|
|
};
|
|
|
|
/**
|
|
* Get the intersection of this set with another set.
|
|
*
|
|
* @param {ve.OrderedHashSet} set Other set
|
|
* @returns {ve.OrderedHashSet} New set containing all values that are both in this set and in set
|
|
*/
|
|
ve.OrderedHashSet.prototype.intersectWith = function ( set ) {
|
|
var newSet = this.clone();
|
|
newSet.removeNotInSet( set );
|
|
return newSet;
|
|
};
|