@@ -132,15 +132,17 @@ public enum Function {
132132 CelFunctionBinding .from ("list_sort" , Collection .class , CelListsExtensions ::sort )),
133133 SORT_BY (
134134 CelFunctionDecl .newFunctionDeclaration (
135- "lists. @sortByAssociatedKeys" ,
136- CelOverloadDecl .newGlobalOverload (
135+ "@sortByAssociatedKeys" ,
136+ CelOverloadDecl .newMemberOverload (
137137 "list_sortByAssociatedKeys" ,
138- "Sorts a list by a key value . Used by the 'sortBy' macro" ,
138+ "Sorts a list by an associated list of keys . Used by the 'sortBy' macro" ,
139139 ListType .create (TypeParamType .create ("T" )),
140- ListType .create (TypeParamType .create ("T" )))),
140+ ListType .create (TypeParamType .create ("T" )),
141+ ListType .create (TypeParamType .create ("U" )))),
141142 CelFunctionBinding .from (
142143 "list_sortByAssociatedKeys" ,
143144 Collection .class ,
145+ Collection .class ,
144146 CelListsExtensions ::sortByAssociatedKeys ));
145147
146148 private final CelFunctionDecl functionDecl ;
@@ -358,8 +360,18 @@ private static List<Object> reverse(Collection<Object> list) {
358360 }
359361 }
360362
363+ private static final CelObjectComparator OBJECT_COMPARATOR = new CelObjectComparator ();
364+
361365 private static ImmutableList <Object > sort (Collection <Object > objects ) {
362- return ImmutableList .sortedCopyOf (new CelObjectComparator (), objects );
366+ if (objects .isEmpty ()) {
367+ return ImmutableList .of ();
368+ }
369+ if (objects .size () == 1 ) {
370+ Object single = objects .iterator ().next ();
371+ OBJECT_COMPARATOR .compare (single , single );
372+ return ImmutableList .copyOf (objects );
373+ }
374+ return ImmutableList .sortedCopyOf (OBJECT_COMPARATOR , objects );
363375 }
364376
365377 private static class CelObjectComparator implements Comparator <Object > {
@@ -383,6 +395,37 @@ public int compare(Object o1, Object o2) {
383395 }
384396 }
385397
398+ private static final String UNUSED_ITER_VAR = "#unused" ;
399+ private static final String SORT_BY_INPUT_VAR = "@__sortBy_input__" ;
400+
401+ /**
402+ * Expands the {@code list.sortBy(var, expr)} receiver macro into a binding expression that sorts
403+ * the target list using keys evaluated by mapping {@code expr} over each element.
404+ *
405+ * <p>For example, given:
406+ *
407+ * <pre>{@code
408+ * myList.sortBy(item, -item.field)
409+ * }</pre>
410+ *
411+ * <p>The macro expands into:
412+ *
413+ * <pre>{@code
414+ * cel.bind(@__sortBy_input__, myList,
415+ * @__sortBy_input__.@sortByAssociatedKeys(
416+ * @__sortBy_input__.map(item, -item.field)
417+ * )
418+ * )
419+ * }</pre>
420+ *
421+ * <p>Where:
422+ *
423+ * <ul>
424+ * <li>{@code @__sortBy_input__.map(item, -item.field)} evaluates the sort key for each element.
425+ * <li>{@code @sortByAssociatedKeys} stably sorts the input list elements based on their
426+ * corresponding sort keys.
427+ * </ul>
428+ */
386429 private static Optional <CelExpr > sortByMacro (
387430 CelMacroExprFactory exprFactory , CelExpr target , ImmutableList <CelExpr > arguments ) {
388431 checkNotNull (exprFactory );
@@ -400,56 +443,86 @@ private static Optional<CelExpr> sortByMacro(
400443 String varName = varIdent .ident ().name ();
401444 CelExpr sortKeyExpr = checkNotNull (arguments .get (1 ));
402445
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 =
446+ // Build map comprehension: @__sortBy_input__.map(varName, sortKeyExpr)
447+ CelExpr targetIdent = exprFactory .newIdentifier (SORT_BY_INPUT_VAR );
448+ CelExpr mapStep =
410449 exprFactory .newGlobalCall (
411450 Operator .ADD .getFunction (),
412451 exprFactory .newIdentifier (exprFactory .getAccumulatorVarName ()),
413- step );
414- // Create an intermediate list and populate it with key-value pairs
415- step =
452+ exprFactory .newList (sortKeyExpr ));
453+ CelExpr mapCompr =
416454 exprFactory .fold (
417455 varName ,
418- target ,
456+ targetIdent ,
419457 exprFactory .getAccumulatorVarName (),
420458 exprFactory .newList (),
421- exprFactory .newBoolLiteral (true ), // Include all elements
422- step ,
459+ exprFactory .newBoolLiteral (true ),
460+ mapStep ,
423461 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 );
426462
427- return Optional .of (step );
463+ // Build call: @__sortBy_input__.@sortByAssociatedKeys(mapCompr)
464+ CelExpr callExpr =
465+ exprFactory .newReceiverCall (
466+ Function .SORT_BY .getFunction (), exprFactory .newIdentifier (SORT_BY_INPUT_VAR ), mapCompr );
467+
468+ // Build bind: cel.bind(@__sortBy_input__, target, callExpr)
469+ CelExpr bindExpr =
470+ exprFactory .fold (
471+ UNUSED_ITER_VAR ,
472+ exprFactory .newList (),
473+ SORT_BY_INPUT_VAR ,
474+ target ,
475+ exprFactory .newBoolLiteral (false ),
476+ exprFactory .newIdentifier (SORT_BY_INPUT_VAR ),
477+ callExpr );
478+
479+ return Optional .of (bindExpr );
428480 }
429481
430- @ SuppressWarnings ({"unchecked" , "rawtypes" })
482+ /**
483+ * Sorts elements of {@code list} based on the natural order of corresponding elements in {@code
484+ * keys}.
485+ *
486+ * <p>Both {@code list} and {@code keys} must have the exact same size. The sorting is stable
487+ * (i.e., preserves the relative order of elements with equal keys).
488+ *
489+ * @param list The input list to sort
490+ * @param keys The associated keys evaluated for each element in {@code list}
491+ * @return A new {@link ImmutableList} containing the elements of {@code list} sorted by {@code
492+ * keys}
493+ */
431494 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 ));
495+ Collection <Object > list , Collection <Object > keys ) {
496+ checkArgument (
497+ list .size () == keys .size (),
498+ "@sortByAssociatedKeys() expected a list of the same size as the associated keys"
499+ + " list, but got %s in list and %s in keys" ,
500+ list .size (),
501+ keys .size ());
502+
503+ int listSize = list .size ();
504+ if (listSize == 0 ) {
505+ return ImmutableList .of ();
438506 }
439- return builder .build ();
440- }
441507
442- private static class CelObjectByKeyComparator implements Comparator <Object > {
443- private final CelObjectComparator keyComparator ;
508+ Object [] listArray = list .toArray ();
509+ Object [] keysArray = keys .toArray ();
510+ if (listSize == 1 ) {
511+ OBJECT_COMPARATOR .compare (keysArray [0 ], keysArray [0 ]);
512+ return ImmutableList .copyOf (list );
513+ }
444514
445- CelObjectByKeyComparator (CelObjectComparator keyComparator ) {
446- this .keyComparator = keyComparator ;
515+ Integer [] indices = new Integer [listSize ];
516+ for (int i = 0 ; i < listSize ; i ++) {
517+ indices [i ] = i ;
447518 }
448519
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+ Arrays .sort (indices , (i1 , i2 ) -> OBJECT_COMPARATOR .compare (keysArray [i1 ], keysArray [i2 ]));
521+
522+ ImmutableList .Builder <Object > builder = ImmutableList .builderWithExpectedSize (listSize );
523+ for (int index : indices ) {
524+ builder .add (listArray [index ]);
453525 }
526+ return builder .build ();
454527 }
455528}
0 commit comments