From 315c29245811790d5d1b3a0f3316a3fca2229c4f Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 27 Feb 2024 21:20:06 -0700 Subject: [PATCH] Add better naming and CFML interop with CFML Arrays and Structs --- models/Stream.cfc | 47 ++++++++++++++++++++++++-- readme.md | 9 +++-- test-harness/tests/specs/MainTests.cfc | 26 +++++++++++++- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/models/Stream.cfc b/models/Stream.cfc index cdec3d5..7f3fbe0 100644 --- a/models/Stream.cfc +++ b/models/Stream.cfc @@ -504,6 +504,14 @@ component accessors="true" { return variables.jStream.toArray(); } + /** + * Returns an array containing the elements of this stream. + * Right now, an alias of toArray(), with the hope that we can convert the `toArray` to a CFML array in the future. + */ + function toNativeArray(){ + return variables.jStream.toArray(); + } + /** * Returns the count of elements in this stream. @@ -787,6 +795,17 @@ component accessors="true" { return variables.jStream.collect( variables.Collectors.toList() ); } + /** + * A mutable reduction operation that accumulates input elements into a mutable result container, optionally transforming the accumulated result into a final representation after all input elements have been processed. + * By default we will collect to a CFML array. + * + * This is a terminal operation. + * + */ + function collectAsArray(){ + return arraySlice( variables.jStream.collect( variables.Collectors.toList() ), 1 ); + } + /** * Returns a Collector implementing a "group by" operation on input elements, grouping elements according to a * classification function, and returning the results in a struct according to the classifier function @@ -919,7 +938,7 @@ component accessors="true" { } /** - * Collect the items to a struct. Please be sure to map the appropriate key and value identifiers + * Collect the items to a HashMap. Please be sure to map the appropriate key and value identifiers * * NOTE: the struct type will only work if the collection we are collecting is a struct or an object * This is a terminal operation. @@ -928,7 +947,7 @@ component accessors="true" { * @valueID If using struct, then we need to know what will be the value key in the collection struct * @overwrite If using struct, then do you overwrite elements if the same key id is found. Defaults is true. */ - function collectAsStruct( + function collectAsMap( required keyID, required valueID, boolean overwrite = true @@ -981,6 +1000,30 @@ component accessors="true" { ); } + /** + * Collect the items to a CFML struct. Please be sure to map the appropriate key and value identifiers + * + * NOTE: the struct type will only work if the collection we are collecting is a struct or an object + * This is a terminal operation. + * + * @keyID If using struct, then we need to know what will be the key value in the collection struct + * @valueID If using struct, then we need to know what will be the value key in the collection struct + * @overwrite If using struct, then do you overwrite elements if the same key id is found. Defaults is true. + */ + function collectAsStruct( + required keyID, + required valueID, + boolean overwrite = true + ){ + var newStruct = {}; + structAppend( + newStruct, + collectAsMap( argumentCollection = arguments ), + true + ); + return newStruct; + } + /** * Returns a Collector which partitions the input elements according to a Predicate, and organizes them into a Map>. * diff --git a/readme.md b/readme.md index 7d96bcc..77d32ad 100644 --- a/readme.md +++ b/readme.md @@ -231,7 +231,8 @@ Terminal operations are a way to finalize the execution of the stream. Please n - `iterator()` - Return a java iterator of the stream - `spliterator()` - Return a java spliterator of the stream - `close()` - Close the stream -- `toArray()` - Convert the stream back into an array +- `toArray()` - Convert the stream back into a native Java array +- `toNativeArray()` - Convert the stream back into a native Java array. Currently and alias of `toArray` with the hope that in the future `toArray` will return a CFML array. - `count()` - Count the elements in the stream - `findAny()` - Find any element in the stream - `findFirst()` - Find the first element in the stream @@ -249,14 +250,16 @@ Terminal operations are a way to finalize the execution of the stream. Please n Collectors are the way to get out of the streams world and obtain a concrete collection of values, like a list, struct, etc. Here are our collector methods available to you: -- `collect()` - Return an array of the final elements. +- `collect()` - Return an ArrayList of the final elements. +- `collectAsArray()` - Return a CFML Array of the final elements. - `collectGroupingBy( classifier )` - Build a final collection according to the classifier lambda/closure that will classify the keys in the group. This is usually a structure of elements. - `collectAverage( mapper, primitive=long )` - Collect an average according to the mapper function/closure. - `collectSum( mapper, primitive=long )` - Collect a sum according to the mapper function/closure. - `collectSummary( mapper, primitive=long )` - Collect a statistics struct according to the mapper function/closure . - `collectAsList( delimiter=",", prefix, suffix )` - Collect results into a string list with a delimiter and attached prefix and/or suffix. - `collectAsSet()` - Collect the items to a set which doesn't include duplicate elements. -- `collectAsStruct( keyID, valueID, overwrite=true )` - Collect the elements into a struct by leveraging the key identifier and the value identifier from the stream of elements to pass into the collection. +- `collectAsMap( keyID, valueID, overwrite=true )` - Collect the elements into a HashMap by leveraging the key identifier and the value identifier from the stream of elements to pass into the collection. +- `collectAsStruct( keyID, valueID, overwrite=true )` - Collect the elements into a CFML struct by leveraging the key identifier and the value identifier from the stream of elements to pass into the collection. - `collectPartitioningBy( predicate )` - partitions the input elements according to a Predicate closure/lambda, and organizes them into a Struct of . diff --git a/test-harness/tests/specs/MainTests.cfc b/test-harness/tests/specs/MainTests.cfc index 1821287..270efbc 100644 --- a/test-harness/tests/specs/MainTests.cfc +++ b/test-harness/tests/specs/MainTests.cfc @@ -425,7 +425,23 @@ } ); given( "The default array collector", function(){ - then( "it will produce an array collection", function(){ + then( "it will produce an array", function(){ + var results = new cbStreams.models.Stream( [ "aa", "aa", "bb", "c", "d", "c" ] ).collect(); + expect( results ).toBeInstanceOf( "java.util.ArrayList" ); + expect( results.size() ).toBe( 6 ); + } ); + } ); + + given( "The CFML array collector", function(){ + then( "it will produce a CFML array", function(){ + var results = new cbStreams.models.Stream( [ "aa", "aa", "bb", "c", "d", "c" ] ).collectAsArray(); + expect( results ).notToBeInstanceOf( "java.util.ArrayList" ); + expect( results.len() ).toBe( 6 ); + } ); + } ); + + given( "The set collector", function(){ + then( "it will produce an set collection", function(){ var setOfNames = new cbStreams.models.Stream( [ "aa", "aa", "bb", "c", "d", "c" ] ).collectAsSet(); expect( setOfNames.size() ).toBe( 4 ); } ); @@ -460,9 +476,17 @@ } ); + given( "The map collector and a key and id mapper", function(){ + then( "it will produce a struct of the collection of those mappers", function(){ + var results = new cbStreams.models.Stream( people ).collectAsMap( "id", "name" ); + expect( results ).toBeInstanceOf( "java.util.HashMap" ); + } ); + } ); + given( "The struct collector and a key and id mapper", function(){ then( "it will produce a struct of the collection of those mappers", function(){ var results = new cbStreams.models.Stream( people ).collectAsStruct( "id", "name" ); + expect( results ).notToBeInstanceOf( "java.util.HashMap" ); expect( results ).toBeStruct(); } ); } );