Improved Apex FluentIterable

My previous implementation of FluentIterable would have been difficult to extend beyond filter and transform. I have rewritten it using the Chain of Responsibility pattern to facilitate adding more Guava FluentIterable capabilities in future.

The essential difference is that Operation is a recursive interface with subclasses that perform the actual filtering or transforming directly.  The essence of the Chain of Responsibility pattern as I use it here is to pass down the list to populate, the element of the list to process, and an iterator representing the current position in the operation stack.  This allows any mix of operations to be added to the stack without altering any of the other operations or the core FluentIterable methods.  New operations can be added purely by adding new Operation subclasses and new methods to add them to the list of operations.

Here's the improved Apex class FluentIterable.cls:

public class FluentIterable {
    private final List<Object> sourceList;
    private final List<Operation> operations;

    private abstract class Operation {
        public abstract void run(List<Object> listToPopulate, Object o, Iterator<Operation> operationIterator);
    }

    private class Transform extends Operation {
        private final Function transform;
        public Transform(Function transform) {
            this.transform = transform;
        }
        public override void run(List<Object> listToPopulate, Object o, Iterator<Operation> operationIterator)
        {
            Object transformed = transform.apply(o);
            if (operationIterator.hasNext()) {
                operationIterator.next().run(listToPopulate, transformed, operationIterator);
            } else {
                listToPopulate.add(transformed);
            }
        }
    }

    private class Filter extends Operation {
        private final Predicate filter;
        public Filter(Predicate filter) {
            this.filter = filter;
        }
        public override void run(List<Object> listToPopulate, Object o, Iterator<Operation> operationIterator) {
            if (filter.apply(o)){
              if (operationIterator.hasNext()) {
                    operationIterator.next().run(listToPopulate, o, operationIterator);
                } else {
                  listToPopulate.add(o);
                }
            }
        }
    }

    public static FluentIterable fromList(List<Object> sourceList) {
        return new FluentIterable(sourceList);
    }

    private FluentIterable(List<Object> sourceList) {
        this.sourceList = sourceList;
        this.operations = new List<Operation>();
    }

    public List<Object> intoList(List<Object> listToPopulate) {
        for (Object o : sourceList) {
            runOperations(listToPopulate, o, operations.iterator());
        }
        return listToPopulate;
    }
    
    private void runOperations(List<Object> listToPopulate, Object o, Iterator<Operation> operationIterator) {
        if (operationIterator.hasNext()) {
            operationIterator.next().run(listToPopulate, o, operationIterator);    
        } else {
            listToPopulate.add(o);
        }
    }

    public FluentIterable filter(Predicate p) {
        operations.add(new Filter(p));
        return this;
    }

    public FluentIterable transform(Function f) {
        operations.add(new Transform(f));
        return this;
    }
}

Here's the updated Apex test class FluentIterableTest.cls:

@isTest
public class FluentIterableTest {
    @isTest static void testPassthru() {
        List<Object> toFilter = new List<Object> {1,2,3,4};
        List<Integer> filtered = (List<Integer>) FluentIterable.fromList(
            toFilter
        ).intoList(
            new List<Integer>()
        );
        System.assertEquals(4, filtered.size());
        System.assertEquals(1, filtered[0]);
        System.assertEquals(2, filtered[1]);
        System.assertEquals(3, filtered[2]);
        System.assertEquals(4, filtered[3]);
    }

    @isTest static void testFilter() {
        List<Object> toFilter = new List<Object> {1,2,3,4};
        List<Integer> filtered = (List<Integer>) FluentIterable.fromList(
            toFilter
        ).filter(
            new EvenPredicate()
        ).intoList(
            new List<Integer>()
        );
        System.assertEquals(2, filtered.size());
        System.assertEquals(2, filtered[0]);
        System.assertEquals(4, filtered[1]);
    }
    
    @isTest static void testTransformAndFilter() {
        List<Object> toFilter = new List<Object> {'1','2','3','4'};
        List<Integer> filtered = (List<Integer>) FluentIterable.fromList(
            toFilter
        ).transform(
            new ToInteger()
        ).filter(
            new EvenPredicate()
        ).intoList(
            new List<Integer>()
        );
        System.assertEquals(2, filtered.size());
        System.assertEquals(2, filtered[0]);
        System.assertEquals(4, filtered[1]);
    }
    
    @isTest static void testFilterTransformFilter() {
        List<String> toFilter = new List<String> {'1','two','2','3','4'};
        List<Integer> filtered = (List<Integer>) FluentIterable.fromList(
            toFilter
        ).filter(
            new IsNumeric()
        ).transform(
            new ToInteger()
        ).filter(
            new EvenPredicate()
        ).intoList(
            new List<Integer>()
        );
        System.assertEquals(2, filtered.size());
        System.assertEquals(2, filtered[0]);
        System.assertEquals(4, filtered[1]);
    }
    
    class ToInteger implements Function {
        public Object apply(Object o) {
            return Integer.valueOf((String) o);
        }
    }
    
    class EvenPredicate implements Predicate {
        public Boolean apply(Object o) {
            return Math.mod((Integer) o, 2) == 0;
        }
    }

    private class IsNumeric implements Predicate {
        private Pattern numericPattern = Pattern.compile('^\\d+$');
        public Boolean apply(Object o) {
            return numericPattern.matcher((String) o).matches();
        }
    }
}

Hopefully you find this useful.