Skip to content

Commit

Permalink
Merge pull request #21 from thalesrc/smart-map
Browse files Browse the repository at this point in the history
Smart map
  • Loading branch information
alisahinozcelik committed Nov 29, 2018
2 parents 09e49f6 + e74739c commit 175308c
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 3 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"document": "typedoc --out docs --excludeNotExported --excludePrivate --disableOutputCheck --gitRevision master --exclude \"**/+(as-proto|as-static)/**/*ts\"",
"prepare-github-pages": "npm run document && echo \"\" > ./docs/.nojekyll",
"test": "nyc mocha --recursive --compilers ts:ts-node/register src/*.spec.ts src/**/*.spec.ts",
"cover": "nyc report --reporter=text-lcov | coveralls"
"cover": "nyc report --reporter=text-lcov | codecov"
},
"author": "alisahinozcelik@gmail.com",
"license": "MIT",
Expand All @@ -43,7 +43,7 @@
"@types/chai": "^4.1.6",
"@types/mocha": "^5.2.5",
"chai": "^4.2.0",
"coveralls": "^3.0.2",
"codecov.io": "^0.1.6",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"ts-node": "^7.0.1",
Expand Down
82 changes: 82 additions & 0 deletions src/smart-map.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { expect, assert } from 'chai';
import 'mocha';

import { SmartMap } from "./smart-map";

describe('SmartMap Class', () => {
let map: SmartMap;

beforeEach(() => {
map = new SmartMap();
});

it('should construct properly', () => {
const mapWithValues = new SmartMap([['key', 'value'], ['key2', 'value2']]);

assert.isDefined(map);
assert.isDefined(mapWithValues);
expect([...mapWithValues.entries()]).to.be.eql([...new Map([['key', 'value'], ['key2', 'value2']]).entries()]);
});

it('should set a value by primitive type keys', () => {
map.set(1, "number");
map.set("string", "string");
map.set(true, "boolean");
map.set(Symbol(), "symbol");
map.set(undefined, "undefined");

expect(map.size).to.eq(5);
});

it('should set a value by object type keys', () => {
const anObj = {};

map.set(anObj, "foo");
expect(map.has(anObj)).to.be.true;
});

it('should store object keys in weakmap storage', () => {
const anObj = {};

map.set(anObj, "foo");

expect(map.size).to.eq(0);
expect(map.has(anObj)).to.be.true;
});

it('should check correctly when has method called via both primitive and object keys', () => {
const anObj = {};

map.set(1, "1");
map.set(anObj, "an object");

expect(map.has(1)).to.be.true;
expect(map.has(anObj)).to.be.true;
});

it('should get value when get method called via both primitive and object keys', () => {
const anObj = {};

map.set(1, "1");
map.set(anObj, "an object");

expect(map.get(1)).to.eq("1");
expect(map.get(anObj)).to.eq("an object");
});

it('should delete value when delete method called via both primitive and object key', () => {
const anObj = {};

map.set(1, "1");
map.set(anObj, "an object");

const primitiveResult = map.delete(1);
const objectResult = map.delete(anObj);

expect(primitiveResult).to.be.true;
expect(objectResult).to.be.true;

expect(map.size).to.eq(0);
expect(map.has(anObj)).to.be.false;
})
});
113 changes: 113 additions & 0 deletions src/smart-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Symbols to protect overriding private methods & properties of SmartMap Class
const KEY__WEAKMAP: unique symbol = Symbol('SmartMap.weakMap');

/**
* ### SmartMap
*
* Like WeakMap but can also store values using primitive keys
*
* * * *
* Example usage:
* ```typescript
* import { SmartMap } from "@thalesrc/js-utils";
*
* const aMap = new SmartMap();
*
* aMap.set("foo", "foo");
* aMap.set(1, "thales rocks");
*
* console.log(aMap.size) // 2
*
* aMap.set({}, "thales rocks again");
* console.log(aMap.size) // 2
*
* const anObject = {};
* aMap.set(anObject, "thales rocks again and again");
* console.log(aMap.size) // 3
* console.log(aMap.get(anObject)) // "thales rocks again and again"
* ```
* * * *
* @template K Typeof Key
* @template V Typeof Value
*/
export class SmartMap<K = any, V = any> extends Map<K, V> {
/**
* Symbol refference of weakmap property
*/
protected static readonly KEY__WEAKMAP = KEY__WEAKMAP;

/**
* Allowed key types to store them in a weakmap instance
*/
public static readonly TYPES_TO_STORE_IN_WEAKMAP = Object.freeze(['object', 'function']);

/**
* The WeakMap object to store values with associated non-primitive (object) keys
*/
protected [KEY__WEAKMAP] = new WeakMap<Object, V>();

/**
* Returns a boolean asserting whether a value has been associated to the key in the SmartMap object or not.
*
* @param key The key of the element to test for presence in the SmartMap object.
* @returns A boolean asserting whether a value has been associated to the key in the SmartMap object or not.
*/
public has(key: K): boolean {
if (this.isStorableInWeakMap(key)) {
return this[KEY__WEAKMAP].has(key);
}

return super.has(key);
}

/**
* Returns the value associated to the key, or undefined if there is none.
*
* @param key The key of the element to return from the SmartMap object.
* @returns The value associated to the key
*/
public get(key: K): V {
if (this.isStorableInWeakMap(key)) {
return this[KEY__WEAKMAP].get(key);
}

return super.get(key);
}

/**
* Sets the value for the key in the SmartMap object.
*
* @param key The key of the element to add to the SmartMap object.
* @param value The value of the element to add to the SmartMap object.
* @returns Returns the SmartMap object.
*/
public set(key: K, value: V): this {
if (this.isStorableInWeakMap(key)) {
this[KEY__WEAKMAP].set(key, value);
} else {
super.set(key, value);
}

return this;
}

/**
* Removes the associated value from the SmartMap object,
*
* @param key The key of the element to remove from the SmartMap object.
* @returns `true` if an element in the SmartMap object existed and has been removed, or `false` if the element does not exist. `SmartMap.prototype.has(key)` will return false afterwards.
*/
public delete(key: K): boolean {
return this[KEY__WEAKMAP].delete(key) || super.delete(key);
}

/**
* Returns a boolean whether if the key is storable in weakmap or not
*
* @param key Key to check if it can be stored in weakmap
* @returns A boolean whether if the key is storable in weakmap or not
*/
protected isStorableInWeakMap(key: K): boolean {
return SmartMap.TYPES_TO_STORE_IN_WEAKMAP.some(_key => typeof key === _key);
}
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true,
"target": "es5",
"target": "es6",
"allowSyntheticDefaultImports": true,
"declaration": true,
"noResolve": false,
Expand Down

0 comments on commit 175308c

Please sign in to comment.