3434import dev .cel .common .types .ListType ;
3535import dev .cel .common .types .SimpleType ;
3636import dev .cel .common .types .TypeParamType ;
37+ import dev .cel .common .values .CelByteString ;
3738import dev .cel .compiler .CelCompilerLibrary ;
3839import dev .cel .parser .CelMacro ;
3940import dev .cel .parser .CelMacroExprFactory ;
4243import dev .cel .runtime .CelInternalRuntimeLibrary ;
4344import dev .cel .runtime .CelRuntimeBuilder ;
4445import dev .cel .runtime .RuntimeEquality ;
46+ import java .time .Duration ;
47+ import java .time .Instant ;
4548import java .util .Arrays ;
4649import java .util .Collection ;
4750import java .util .Comparator ;
4851import java .util .Iterator ;
4952import java .util .List ;
53+ import java .util .Map ;
5054import java .util .Optional ;
5155import java .util .Set ;
5256
@@ -132,16 +136,21 @@ public enum Function {
132136 CelFunctionBinding .from ("list_sort" , Collection .class , CelListsExtensions ::sort )),
133137 SORT_BY (
134138 CelFunctionDecl .newFunctionDeclaration (
135- "lists. @sortByAssociatedKeys" ,
136- CelOverloadDecl .newGlobalOverload (
139+ "@sortByAssociatedKeys" ,
140+ CelOverloadDecl .newMemberOverload (
137141 "list_sortByAssociatedKeys" ,
138- "Sorts a list by a key value . Used by the 'sortBy' macro" ,
142+ "Sorts a list by associated keys . Used by the 'sortBy' macro" ,
139143 ListType .create (TypeParamType .create ("T" )),
140- ListType .create (TypeParamType .create ("T" )))),
144+ ListType .create (TypeParamType .create ("T" )),
145+ ListType .create (TypeParamType .create ("U" )))),
141146 CelFunctionBinding .from (
142147 "list_sortByAssociatedKeys" ,
143- Collection .class ,
144- CelListsExtensions ::sortByAssociatedKeys ));
148+ ImmutableList .of (Collection .class , Collection .class ),
149+ (args ) -> {
150+ Collection <Object > target = (Collection <Object >) args [0 ];
151+ Collection <Object > keys = (Collection <Object >) args [1 ];
152+ return CelListsExtensions .sortByAssociatedKeys (target , keys );
153+ }));
145154
146155 private final CelFunctionDecl functionDecl ;
147156 private final ImmutableSet <CelFunctionBinding > functionBindings ;
@@ -222,7 +231,7 @@ public ImmutableSet<CelFunctionDecl> functions() {
222231
223232 @ Override
224233 public ImmutableSet <CelMacro > macros () {
225- if (version >= 2 ) {
234+ if (version >= 2 || ( version == - 1 && functions . contains ( Function . SORT_BY )) ) {
226235 return ImmutableSet .of (
227236 CelMacro .newReceiverMacro ("sortBy" , 2 , CelListsExtensions ::sortByMacro ));
228237 }
@@ -300,7 +309,10 @@ private static ImmutableList<Object> flatten(Collection<Object> list, long depth
300309 }
301310
302311 public static ImmutableList <Long > genRange (long end ) {
303- ImmutableList .Builder <Long > builder = ImmutableList .builder ();
312+ checkArgument (end >= 0 , "lists.range: size must be non-negative, got %s" , end );
313+ checkArgument (end <= 1_000_000 , "lists.range: size %s exceeds maximum allowed (1000000)" , end );
314+
315+ ImmutableList .Builder <Long > builder = ImmutableList .builderWithExpectedSize ((int ) end );
304316 for (long i = 0 ; i < end ; i ++) {
305317 builder .add (i );
306318 }
@@ -359,6 +371,17 @@ private static List<Object> reverse(Collection<Object> list) {
359371 }
360372
361373 private static ImmutableList <Object > sort (Collection <Object > objects ) {
374+ if (objects .isEmpty ()) {
375+ return ImmutableList .of ();
376+ }
377+ for (Object element : objects ) {
378+ if (!isSupportedComparableType (element )) {
379+ throw new IllegalArgumentException ("List elements must be comparable" );
380+ }
381+ }
382+ if (objects .size () < 2 ) {
383+ return ImmutableList .copyOf (objects );
384+ }
362385 return ImmutableList .sortedCopyOf (new CelObjectComparator (), objects );
363386 }
364387
@@ -369,12 +392,14 @@ private static class CelObjectComparator implements Comparator<Object> {
369392 @ SuppressWarnings ({"unchecked" })
370393 @ Override
371394 public int compare (Object o1 , Object o2 ) {
395+ if (o1 == null || o2 == null ) {
396+ throw new IllegalArgumentException ("List elements must be comparable" );
397+ }
372398 if (o1 instanceof Number && o2 instanceof Number ) {
373399 return ComparisonFunctions .numericCompare ((Number ) o1 , (Number ) o2 );
374400 }
375-
376- if (!(o1 instanceof Comparable )) {
377- throw new IllegalArgumentException ("List elements must be comparable" );
401+ if (isByteType (o1 ) && isByteType (o2 )) {
402+ return compareBytes (o1 , o2 );
378403 }
379404 if (o1 .getClass () != o2 .getClass ()) {
380405 throw new IllegalArgumentException ("List elements must have the same type" );
@@ -383,6 +408,45 @@ public int compare(Object o1, Object o2) {
383408 }
384409 }
385410
411+ private static boolean isByteType (Object obj ) {
412+ return obj instanceof CelByteString || obj instanceof byte [];
413+ }
414+
415+ private static int compareBytes (Object o1 , Object o2 ) {
416+ if (o1 instanceof CelByteString && o2 instanceof CelByteString ) {
417+ return CelByteString .unsignedLexicographicalComparator ()
418+ .compare ((CelByteString ) o1 , (CelByteString ) o2 );
419+ }
420+
421+ byte [] b1 = o1 instanceof CelByteString ? ((CelByteString ) o1 ).toByteArray () : (byte []) o1 ;
422+ byte [] b2 = o2 instanceof CelByteString ? ((CelByteString ) o2 ).toByteArray () : (byte []) o2 ;
423+
424+ int minLength = Math .min (b1 .length , b2 .length );
425+ for (int i = 0 ; i < minLength ; i ++) {
426+ int result = Integer .compare (Byte .toUnsignedInt (b1 [i ]), Byte .toUnsignedInt (b2 [i ]));
427+ if (result != 0 ) {
428+ return result ;
429+ }
430+ }
431+ return Integer .compare (b1 .length , b2 .length );
432+ }
433+
434+ private static boolean isSupportedComparableType (Object obj ) {
435+ if (obj == null ) {
436+ return false ;
437+ }
438+ if (obj instanceof Number
439+ || obj instanceof Boolean
440+ || obj instanceof String
441+ || obj instanceof CelByteString
442+ || obj instanceof byte []
443+ || obj instanceof Duration
444+ || obj instanceof Instant ) {
445+ return true ;
446+ }
447+ return obj instanceof Comparable && !(obj instanceof Collection ) && !(obj instanceof Map );
448+ }
449+
386450 private static Optional <CelExpr > sortByMacro (
387451 CelMacroExprFactory exprFactory , CelExpr target , ImmutableList <CelExpr > arguments ) {
388452 checkNotNull (exprFactory );
@@ -400,56 +464,79 @@ private static Optional<CelExpr> sortByMacro(
400464 String varName = varIdent .ident ().name ();
401465 CelExpr sortKeyExpr = checkNotNull (arguments .get (1 ));
402466
403- // Compute the key using the second argument of the `sortBy(e, key)` macro.
404- // Combine the key and the value in a two-element list
405- CelExpr step = exprFactory .newList (sortKeyExpr , varIdent );
406- // Wrap the pair in another list in order to be able to use the `list+list` operator
407- step = exprFactory .newList (step );
408- // Append the key-value pair to the i
409- step =
467+ String sortByInputVar = "@__sortBy_input__" ;
468+ CelExpr sortByInputIdent = exprFactory .newIdentifier (sortByInputVar );
469+
470+ // Map comprehension: target.map(varName, sortKeyExpr)
471+ CelExpr mapStep =
410472 exprFactory .newGlobalCall (
411473 Operator .ADD .getFunction (),
412474 exprFactory .newIdentifier (exprFactory .getAccumulatorVarName ()),
413- step );
414- // Create an intermediate list and populate it with key-value pairs
415- step =
475+ exprFactory .newList (sortKeyExpr ));
476+ CelExpr mapCompr =
416477 exprFactory .fold (
417478 varName ,
418- target ,
479+ sortByInputIdent ,
419480 exprFactory .getAccumulatorVarName (),
420481 exprFactory .newList (),
421- exprFactory .newBoolLiteral (true ), // Include all elements
422- step ,
482+ exprFactory .newBoolLiteral (true ),
483+ mapStep ,
423484 exprFactory .newIdentifier (exprFactory .getAccumulatorVarName ()));
424- // Finally, sort the list of key-value pairs and map it to a list of values
425- step = exprFactory .newGlobalCall (Function .SORT_BY .getFunction (), step );
426485
427- return Optional .of (step );
486+ // Receiver call: sortByInputIdent.@sortByAssociatedKeys(mapCompr)
487+ CelExpr callExpr =
488+ exprFactory .newReceiverCall (
489+ Function .SORT_BY .getFunction (),
490+ sortByInputIdent ,
491+ mapCompr );
492+
493+ // cel.bind(sortByInputVar, target, callExpr)
494+ CelExpr bindExpr =
495+ exprFactory .fold (
496+ "#unused" ,
497+ exprFactory .newList (),
498+ sortByInputVar ,
499+ target ,
500+ // Loop condition is false because this comprehension simulates a local variable assignment (`bind`), rather than a traditional iteration.
501+ exprFactory .newBoolLiteral (false ),
502+ sortByInputIdent ,
503+ callExpr );
504+
505+ return Optional .of (bindExpr );
428506 }
429507
430- @ SuppressWarnings ({"unchecked" , "rawtypes" })
431508 private static ImmutableList <Object > sortByAssociatedKeys (
432- Collection <List <Object >> keyValuePairs ) {
433- List <Object >[] array = keyValuePairs .toArray (new List [0 ]);
434- Arrays .sort (array , new CelObjectByKeyComparator (new CelObjectComparator ()));
435- ImmutableList .Builder <Object > builder = ImmutableList .builderWithExpectedSize (array .length );
436- for (List <Object > pair : array ) {
437- builder .add (pair .get (1 ));
509+ Collection <Object > list , Collection <Object > keys ) {
510+ if (list .size () != keys .size ()) {
511+ throw new IllegalArgumentException (
512+ String .format (
513+ "@sortByAssociatedKeys() expected a list of the same size as the associated keys"
514+ + " list, but got %d and %d elements respectively." ,
515+ list .size (), keys .size ()));
438516 }
439- return builder .build ();
440- }
441-
442- private static class CelObjectByKeyComparator implements Comparator <Object > {
443- private final CelObjectComparator keyComparator ;
444-
445- CelObjectByKeyComparator (CelObjectComparator keyComparator ) {
446- this .keyComparator = keyComparator ;
517+ if (list .isEmpty ()) {
518+ return ImmutableList .of ();
447519 }
448-
449- @ SuppressWarnings ({"unchecked" })
450- @ Override
451- public int compare (Object o1 , Object o2 ) {
452- return keyComparator .compare (((List <Object >) o1 ).get (0 ), ((List <Object >) o2 ).get (0 ));
520+ for (Object key : keys ) {
521+ if (!isSupportedComparableType (key )) {
522+ throw new IllegalArgumentException ("List elements must be comparable" );
523+ }
524+ }
525+ if (list .size () < 2 ) {
526+ return ImmutableList .copyOf (list );
453527 }
528+ Object [] listArray = list .toArray ();
529+ Object [] keysArray = keys .toArray ();
530+ Integer [] indices = new Integer [listArray .length ];
531+ for (int i = 0 ; i < indices .length ; i ++) {
532+ indices [i ] = i ;
533+ }
534+ CelObjectComparator comparator = new CelObjectComparator ();
535+ Arrays .sort (indices , (i1 , i2 ) -> comparator .compare (keysArray [i1 ], keysArray [i2 ]));
536+ ImmutableList .Builder <Object > builder = ImmutableList .builderWithExpectedSize (indices .length );
537+ for (int idx : indices ) {
538+ builder .add (listArray [idx ]);
539+ }
540+ return builder .build ();
454541 }
455542}
0 commit comments