'use strict';
const HatByteProcessor = require('./hat-processor').HatByteProcessor;
/**
* A Sorting Hat
* @class
* @param {ArrayBuffer} arrayBuffer - allocate this Hat with some working memory
* @throws {TypeError} if arrayBuffer is not an instance of ArrayBuffer
*/
function Hat(arrayBuffer) {
if (!(arrayBuffer instanceof ArrayBuffer)) {
throw new TypeError('argument not an instance of ArrayBuffer');
}
this.arrayBuffer = arrayBuffer;
this.dataView = new DataView(this.arrayBuffer);
}
/**
* Compare two hat instances on data contents
*
* @param {Hat} hat - instance of <b>a</b> Hat
* @returns {boolean} if instance (or contents) are the same
* @throws {TypeError} if hat is not an instance of Hat
*/
Hat.prototype.compare = function (hat) {
let same = false;
if (!(hat instanceof Hat)) {
throw new TypeError('hat is not an instance of Hat');
}
const arrayBuffer = hat.arrayBuffer;
const thisArrayBuffer = this.arrayBuffer;
if (thisArrayBuffer === arrayBuffer) {
same = true;
} else {
const thisDataView = this.dataView;
const dataView = hat.dataView;
const byteLength = Math.max(thisArrayBuffer.byteLength, arrayBuffer.byteLength);
let result = true;
for (let i = 0; i < byteLength && result; i++) {
const value1 = (i < thisDataView.byteLength) ? thisDataView.getUint8(i) : 0x0;
const value2 = (i < dataView.byteLength) ? dataView.getUint8(i) : 0x0;
if (value1 !== value2) {
result = false;
}
}
same = result;
}
return same;
};
/**
* This method analyzes every bit value in this hat and creates the corresponding <br>
* position array where bits are logical true.
*
* @example
*
*
* // Initialize a Hat with ArrayBuffer (size = 2 bytes)
* const ab = new ArrayBuffer(2);
* const hat = new Hat(ab);
*
* // Pre set some bit positions
* hat.setPosition(1)
* .setPosition(2)
* .setPosition(3)
* .setPosition(4)
* .setPosition(5)
* .setPosition(6)
* .setPosition(7)
* .setPosition(8)
* .setPosition(10);
*
* hat.getPositionsArray(); // returns: [1, 2, 3, 4, 5, 6, 7, 8, 10]
*
*
* @returns {Array} Which contains bit positions from this hat, which are logical set to true
*/
Hat.prototype.getPositionsArray = function () {
const dataView = this.dataView;
const byteLength = this.arrayBuffer.byteLength;
const positions = [];
for (let byteNo = 0; byteNo < byteLength; byteNo++) {
const value = dataView.getUint8(byteNo);
for (let bitPos = 0; bitPos < 8; bitPos++) {
const posValue = HatByteProcessor.isTrue(value, bitPos);
if (posValue) {
positions.push(byteNo * 8 + bitPos + 1);
}
}
}
return positions;
};
/**
* Set bit: true according to integer position in the Sorting Hat <br>
* <i>Note: Starting from 1</i>
* @throws {Error} Position can't exceed data view bounds.
* @param {Number} position - unsigned integer value
* @returns {Hat} - For chaining purposes
*/
Hat.prototype.setPosition = function (position) {
const bitBytePosition = HatByteProcessor.getByteNoAndBitPos(position);
const dataViewBounds = this.dataView.byteLength;
if (bitBytePosition.byteNo < dataViewBounds) {
let byte = this.dataView.getUint8(bitBytePosition.byteNo);
byte = HatByteProcessor.setBit(byte, bitBytePosition.bitPos);
this.dataView.setUint8(bitBytePosition.byteNo, byte);
} else {
// Because we can't exceed the amount of allocated bytes,
// Please ensure position ends within the allocated memory
throw new Error('Illegal position');
}
return this;
};
/**
* Set bit: false according to integer position in the Sorting Hat
* Note: Starting from 1
* @param {Number} position - unsigned integer value
* @returns {Hat}
*/
Hat.prototype.clearPosition = function (position) {
const bitBytePosition = HatByteProcessor.getByteNoAndBitPos(position);
let byte = this.dataView.getUint8(bitBytePosition.byteNo);
byte = HatByteProcessor.clearBit(byte, bitBytePosition.bitPos);
this.dataView.setUint8(bitBytePosition.byteNo, byte);
return this;
};
/**
* Toggle bit value true => false, false => true
* @example
*
* const hat = new Hat(ArrayBuffer(2));
* hat.changePosition(1, true);
*
* hat.isPosition(1); // true
*
* @param {Number} position - unsigned integer value
* @returns {Hat}
*/
Hat.prototype.togglePosition = function (position) {
const bitBytePosition = HatByteProcessor.getByteNoAndBitPos(position);
let byte = this.dataView.getUint8(bitBytePosition.byteNo);
byte = HatByteProcessor.toggleBit(byte, bitBytePosition.bitPos);
this.dataView.setUint8(bitBytePosition.byteNo, byte);
return this;
};
/**
* Set value for a bit on position
*
* @example
*
* const hat = new Hat(ArrayBuffer(2));
* hat.changePosition(1, true);
*
* hat.isPosition(1); // returns true
*
*
* @param {Number} position - unsigned integer value
* @param {boolean} [on] - Optional set true or false (default : false)
* @returns {Hat} - Return itself for chaining purposes
*/
Hat.prototype.changePosition = function (position, on = false) {
const bitBytePosition = HatByteProcessor.getByteNoAndBitPos(position);
let byte = this.dataView.getUint8(bitBytePosition.byteNo);
byte = HatByteProcessor.changeBit(byte, bitBytePosition.bitPos, on);
this.dataView.setUint8(bitBytePosition.byteNo, byte);
return this;
};
/**
* Checks if a value is true or false on a specific position
*
*
* @example
*
* const hat = new Hat(ArrayBuffer(2));
* hat.setPosition(1);
*
* hat.isPosition(1); // returns true
*
* @param {Number} position - unsigned integer value
* @returns {boolean} if membership is set
*/
Hat.prototype.isPosition = function (position) {
const bitBytePosition = HatByteProcessor.getByteNoAndBitPos(position);
let byte = this.dataView.getUint8(bitBytePosition.byteNo);
const result = HatByteProcessor.isTrue(byte, bitBytePosition.bitPos);
return result;
};
/**
* Compares all given positions
* - A positive position means this position should be set truthy
* - A negative position means this position should be set falsy
*
* When wants to check on bit positions outside the memory bounds (dataViewBounds), <br>
* method wil return early with falsy result
*
*
* @example
*
* // Set 2 bit positions to logical '1'
* const hat = new Hat(new ArrayBuffer(2));
* hat.setPosition(1).setPosition(2);
*
* hat.hasAllFromPositions([1, 2]); // true
*
* @example
*
* // Set 2 bit positions to logical '1' then the first bit position back to '0'
* const hat = new Hat(new ArrayBuffer(2));
* hat.setPosition(1).setPosition(2),togglePosition(1);
*
* hat.hasAllFromPositions([-1, 2]); // true
*
* @param {Array<Number>} positionArray - containing signed integer values (representing bit positions)
* @return {boolean} true when all positions correspondent to the given indexes
* @throws {TypeError} if positionArray is not an instance of array
*
*/
Hat.prototype.hasAllFromPositions = function (positionArray = []) {
const self = this;
const dataViewBounds = self.dataView.byteLength;
if (!(Array.isArray(positionArray))) {
throw new TypeError('positionArray not an instance of Array');
}
// Shallow copy
let positions = positionArray.slice();
// Remove duplicate values:
positions = positions.filter(function (value, index, self) {
return self.indexOf(value) === index;
});
// Check bits on position if they are truthy (positive position) or falsy (negative position)
let hasAllFromPositions = true;
// Exit early when one of the positions is not logic true.
for (let i = 0; i < positions.length && hasAllFromPositions; i++) {
const position = positions[i];
const positionAbs = Math.abs(position);
const bitBytePosition = HatByteProcessor.getByteNoAndBitPos(positionAbs);
// Check if position is not out of bound of the data view:
if (bitBytePosition.byteNo < dataViewBounds) {
const byte = self.dataView.getUint8(bitBytePosition.byteNo);
if (position >= 0) {
hasAllFromPositions = HatByteProcessor.isTrue(byte, bitBytePosition.bitPos);
} else {
hasAllFromPositions = HatByteProcessor.isFalse(byte, bitBytePosition.bitPos);
}
} else {
// Position is outside the bounds of the DataView, skipping the rest...
hasAllFromPositions = false;
}
}
return hasAllFromPositions;
};
/**
* Able to manipulate bits according to an array of signed integers
*
* @example
*
* // Set 2 bit positions to logical '1'
* const hat = Hat.create(2);
* hat.setAllFromPositions([1, 2]);
* hat.hasAllFromPositions([1, 2]); // returns true
*
* @example
*
* // Set 1 bit positions to logical '1' and the second to '0'
* const hat = Hat.create(2);
* hat.setAllFromPositions([1, -2]);
* hat.hasAllFromPositions([1]); // returns true
*
* @param {Array<Number>} positionArray - Array with integer values starting from 1.
* @returns {Hat} - For chaining purposes
* @throws {TypeError} if positionArray is not an instance of array
*/
Hat.prototype.setAllFromPositions = function (positionArray = []) {
const self = this;
const dataViewBounds = self.dataView.byteLength;
if (!(Array.isArray(positionArray))) {
throw new TypeError('positionArray not an instance of Array');
}
// Shallow copy
let positions = positionArray.slice();
// Remove duplicate values:
positions = positions.filter(function (value, index, self) {
return self.indexOf(value) === index;
});
for (let i = 0; i < positions.length; i++) {
const position = positions[i];
const positionAbs = Math.abs(position);
const bitBytePosition = HatByteProcessor.getByteNoAndBitPos(positionAbs);
// Check if position is not out of bound of the data view:
if (bitBytePosition.byteNo < dataViewBounds) {
let byte = self.dataView.getUint8(bitBytePosition.byteNo);
byte = HatByteProcessor.changeBit(byte, bitBytePosition.bitPos, (position >= 0));
self.dataView.setUint8(bitBytePosition.byteNo, byte);
}
}
return self;
};
/**
* Convert the whole ArrayBuffer to a string
* (Hint: Could be used to store a hat in persistent storage as a encoded string)
*
* @returns {string} - Encoded string
* @override
*/
Hat.prototype.toString = function () {
return String.fromCharCode.apply(null, new Uint16Array(this.arrayBuffer));
};
/**
* Class method to create a new Hat from a string
* (Could be used to retrieve from persistent storage)
* @param {string} str - Representing a new Hat instance
* @returns {Hat} - new instance of a Hat
* @throws {TypeError} if given argument is not an instance of string
*/
Hat.fromString = function (str = '') {
if (typeof str !== 'string') {
throw new TypeError('argument not a string');
}
const arrayBuffer = new ArrayBuffer(str.length * 2); // 2 bytes for each char
const typedArray = new Uint16Array(arrayBuffer);
for (let i = 0, strLen = str.length; i < strLen; i++) {
typedArray[i] = str.charCodeAt(i);
}
return new Hat(arrayBuffer);
};
/**
* Creates a new empty Hat from a given byte size
* @param {Number} byteSize - Allocate this Hat with a unsigned integer value (size in bytes)
* @returns {Hat} - new instance of a Hat
*/
Hat.create = function (byteSize) {
const arrayBuffer = new ArrayBuffer(byteSize);
return new Hat(arrayBuffer);
};
module.exports = Hat;