Skip to content
Open
142 changes: 129 additions & 13 deletions core/converter/collection-iteration-converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* @requires mod/core/converter/converter
*/
const Converter = require("./converter").Converter,
evaluate = require("../frb/evaluate"),
Promise = require("../promise").Promise;


Expand All @@ -18,6 +19,10 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti

serializeSelf: {
value: function (serializer) {
serializer.setProperty("convertedValueIteratorExpression", this.convertedValueIteratorExpression);
serializer.setProperty("iterator", this.iterator);
serializer.setProperty("iterationConverter", this.iterationConverter);
serializer.setProperty("iterationReverter", this.iterationReverter);

serializer.setProperty("mapConverter", this.keysConverter);
serializer.setProperty("mapReverter", this.keysConverter);
Expand All @@ -27,6 +32,27 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
deserializeSelf: {
value: function (deserializer) {

let value = deserializer.getProperty("iterator");
if (value) {
this.iterator = value;
}

value = deserializer.getProperty("convertedValueIteratorExpression");
if (value) {
this.convertedValueIteratorExpression = value;
}


value = deserializer.getProperty("iterationConverter");
if (value) {
this.iterationConverter = value;
}
value = deserializer.getProperty("iterationReverter");
if (value) {
this.iterationReverter = value;
}


value = deserializer.getProperty("mapConverter");
if (value) {
this.mapConverter = value;
Expand All @@ -39,6 +65,64 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
}
},

/**
* Sometimes it might be more practocal to get an iterator from the value to be converted, like for an array or a map. A map especially
* offers both keys() and values() iterators. So setting "keys" as the value for convertedValueIteratorExpression, will lead a CollectionIterationConverter
* to evaluate that expression on the value being converted and get the iterator it needs.
*
* @property {Iterator|function}
* @default {Iterator} undefined
*/
_convertedValueIteratorExpression: {
value: undefined
},
convertedValueIteratorExpression: {
get: function() {
return this._convertedValueIteratorExpression;
},
set: function(value) {
if(value !== this._convertedValueIteratorExpression) {
this._convertedValueIteratorExpression = value;
}
}
},

/**
* The iterator object to be used to iterate over the collection to be converted. The iterator can be what turns one object into a collection
* For example, a single object with an ExpressionIterator will produce a collection of values to convert.
*
* @property {Iterator|function}
* @default {Iterator} undefined
*/
_iterator: {
value: undefined
},
iterator: {
get: function() {
return this._iterator;
},
set: function(value) {
if(value !== this._iterator) {
this._iterator = value;
}
}
},


/**
* @property {Converter|function}
* @default {Converter} undefined
*/
iterationConverter: {
get: function() {
return this._iterationConverter;
},
set: function(value) {
this._iterationConverter = value;
this._convert = this._convertCollection;
}
},

/**
* @property {Converter|function}
* @default {Converter} undefined
Expand All @@ -49,8 +133,8 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
},
set: function(value) {
this._iterationConverter = value;
this._convert = this._convertElementIndexCollection;
this._revert = this._revertElementIndexCollection;
this._convert = this._convertCollection;
this._revert = this._revertCollection;
}
},

Expand All @@ -64,8 +148,8 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
},
set: function(value) {
this._iterationReverter = value;
this._convert = this._convertElementIndexCollection;
this._revert = this._revertElementIndexCollection;
this._convert = this._convertCollection;
this._revert = this._revertCollection;
}
},

Expand Down Expand Up @@ -136,13 +220,34 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
* @param {Collection} value - a collection where this._iterationConverter is applied on each value
* @returns {Collection} a collection of the same type as the input containing each value converted.
*/
_convertElementIndexCollection: {
_convertCollection: {
value: function (value) {

if(!this._iterationConverter || !value ) return value;

var values = value.values(),
converter = this._iterationConverter,
//If value is not a collection, we make an effort to treat it as an iteration object
// if(isNaN(value.length) || isNaN(value.size)) {
// return this._iterationConverter.convert(value);
// }

/*
A pre-set iterator can't know the argument valuet is what it needs to iterate on,
so we use the .from() method to make it aware of it.

However the other methods are asking value for it, so using .from(value) is not needed.
*/
var valueIterator = this._iterator
? this._iterator.from(value)
: this.convertedValueIteratorExpression
? evaluate(this.convertedValueIteratorExpression)
: value[Symbol.iterator](),
isValueCollection = (!isNaN(value.length) || !isNaN(value.size));

if(!valueIterator) {
throw "No Iterator found for value:", value;
}

var converter = this._iterationConverter,
iteration,
isConverterFunction = typeof converter === "function",
iValue,
Expand All @@ -151,7 +256,7 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
promises,
result;

while(!(iteration = values.next()).done) {
while(!(iteration = valueIterator.next()).done) {
iValue = iteration.value;
iConvertedValue =
isConverterFunction
Expand All @@ -161,7 +266,18 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
if(Promise.is(iConvertedValue)) {
(promises || (promises = [])).push(iConvertedValue);
} else {
(result || (result = new value.constructor)).add(iConvertedValue);
/*
If we don't have result yet, we create it to be of the same type of the value we received
TODO: We might need to add another property to fully control that type from the outside if needed
Like for receiving an array but returning a set
*/
if(!isValueCollection) {
if(!result) {
result = value;
}
} else {
(result || (result = new value.constructor)).add(iConvertedValue);
}
}
index++;
}
Expand All @@ -180,13 +296,13 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
* @param {Collection} value - a collection where this._iterationReverter is applied on each value
* @returns {Collection} a collection of the same type as the input containing each value reverted.
*/
_revertElementIndexCollection: {
_revertCollection: {
enumerable: false,
value: function(value) {

if(!this._iterationReverter || !value) return value;

var values = value.values(),
var valueIterator = value.values(),
reverter = this._iterationReverter,
iteration,
isReverterFunction = typeof reverter === "function",
Expand All @@ -196,11 +312,11 @@ exports.CollectionIterationConverter = Converter.specialize( /** @lends Collecti
promises,
result;

if(!isReverterFunction && typeof reverter.revert !== "function") {
if(!isReverterFunction && typeof g.revert !== "function") {
return value;
}

while(!(iteration = values.next()).done) {
while(!(iteration = valueIterator.next()).done) {
iValue = iteration.value;
iConvertedValue =
isReverterFunction
Expand Down
5 changes: 4 additions & 1 deletion core/converter/pipeline-converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ exports.PipelineConverter = Converter.specialize({


if (isFinalOutput) {
result = isPromise ? output : Promise.resolve(output);
//Potentially breaking change here. The code was introducing a Promise in the output when none existed in any of the converter involved
//WAS: result = isPromise ? output : Promise.resolve(output);
//NOW: respecting the natural outcome of the pipeline's converters
result = output;
} else if (isPromise) {
result = output.then(function (value) {
return self._convertWithConverterAtIndex(value, index);
Expand Down
76 changes: 69 additions & 7 deletions core/expression-iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ exports.ExpressionIterator = class ExpressionIterator extends Object {
if(value) {
this._value = value;
this._expression = expression;
/*
Initially, during the creation of the iterator, we need to call it because the next method is actually a generator, so by invoking it we return new instance of the generator.
*/
this._iterator = this._generateNext(this._expression, value);
}
}

Expand All @@ -41,6 +37,12 @@ exports.ExpressionIterator = class ExpressionIterator extends Object {
* @private
* @type {object}
*/
__iterator: {
value: null,
},
_expression: {
value: null,
},
_syntax: {
value: null,
},
Expand All @@ -58,6 +60,47 @@ exports.ExpressionIterator = class ExpressionIterator extends Object {

}

/**
* Serializes the ExpressionIterator's properties using the provided serializer.
* @param {Serializer} serializer - The serializer instance.
*/
serializeSelf(serializer) {
super.serializeSelf(serializer);
serializer.setProperty("expression", this.expression);
}

/**
* Deserializes the ExpressionIterator's properties using the provided deserializer.
* @param {Deserializer} deserializer - The deserializer instance.
*/
deserializeSelf(deserializer) {
this.expression = deserializer.getProperty("expression");
}


/*
* Borrowed from Iterator.from() static method
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/from
*
* Allows a configured instance to iterate over a specific value
* @param {Iterable} value - An objec to iterate on.
* @return {this}
*/
from(value) {
this._value = value;
return this;
}

get _iterator() {
return this.__iterator || (this.__iterator = this._generateNext(this._expression));
}

/**
* TEST ME - to see if expression were changed while
* iteration is happening if it does the right thing
*
* @type {object}
*/
_reset() {
this._expression = null;
this._compiledSyntax = null;
Expand All @@ -68,6 +111,17 @@ exports.ExpressionIterator = class ExpressionIterator extends Object {
this._syntax = null;
}

get expression() {
return this._expression;
}
set expression (value) {
if (value !== this._expression) {
//We need to reset:
this._reset();
this._expression= value;
}
}

/**
* The parsed expression, a syntactic tree.
* Now mutable to avoid creating new objects when appropriate
Expand Down Expand Up @@ -135,9 +189,17 @@ exports.ExpressionIterator = class ExpressionIterator extends Object {
} else {
this._current = this.evaluateExpression(this._current);
}
yield this._current;

/*
To have the yiels return {value:..., done: true},
the last yield needs to be the one to cary
the last actual value, done: will be false
the function needs to end without a yield
then {value:undefined, done: true} is returned by next()
*/
if(this._current) {
yield this._current;
}
}

}

}
10 changes: 10 additions & 0 deletions core/extras/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,13 @@ Object.defineProperty(Function.prototype, "isClass", {
configurable: true
});

Object.defineProperty(Function.prototype, "debounceWithDelay", {
value: function (delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => this(...args), delay)
}
},
configurable: true
});
18 changes: 18 additions & 0 deletions data/converter/data-collection-iteration-converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ exports.DataCollectionIterationConverter = class DataCollectionIterationConverte
this._iterationConverter.foreignDescriptor = value;
}
}

convert(value) {
if(this.currentRule?.propertyDescriptor.cardinality === 1) {
if(Array.isArray(value)) {
if(value.length === 1) {
return super.convert(value.one());
} else {
throw `convert value with length > 1 for property ${this.currentRule.propertyDescriptor.name} with a cardinality of 1`
}

} else {
throw `Collection other than array are not handled for a property ${this.currentRule.propertyDescriptor.name} with a cardinality of 1: ${value}`;
}

} else {
return super.convert(value);
}
}
}


Loading