Recursive generics and pipeline pattern fluent API

Viewed 61

I'm trying to implement a hierarchical fluent interface that acts like the pipeline pattern, as a sequence of segments, operations in which the output of an operation is the input of the next one...
More or less, i want to have something like this :

Pipeline<String> pipeline = Pipeline<String>.newPipe()
    .segments()
        .segment()
            .operation()
                .op(new Operation()) // from String to Integer
                .someConfiguration()...
            .done()
            .operation()
                .op(new Operation2()) // from Integer to Map
                .someConfiguration()...
            .done()
            .segments()
                .segment()
                    .operation()
                        .....
                    .end()
                    ...
                .done()
                .segment()
                    ....
                .done()
            .done()
        .done()
    .done()
.build();

So, i tried something like this, with recursive generics :

public class Base<U, S, T extends Base<S, ?, ?>> {

}

as a base class, and the child classes :

    public class Segments<U, S, T extends Base<S, ?, ?>> extends Base<U, S, T> {

        private T parent;

        public Segments(T parent) {
            this.parent = parent;
        }

        public Segment<U, U, Segments<U, S, T>> segment() {
            return new Segment<>(this);
        }

        public <A> Segments<A, S, T> returns(String expression, Class<A> clazz) {
            return (Segments<A, S, T>) this;
        }

        public T done() {
            return parent;
        }

    }

    public class Segment<U, S, T extends Base<S, ?, ?>> extends Base<U, S, T> {

        private Op<U, U, Segment<U, S, T>> op;

        private Segments<U, U, Segment<U, S, T>> segments;

        private T parent;

        public Segment(T parent) {
            this.parent = parent;
            this.segments = new Segments<>(this);
            this.op = new Op<>(this);
        }

        public Op<U, U, Segment<U, S, T>> op() {
            return op;
        }

        public Segments<U, U, Segment<U, S, T>> segments() {
            return this.segments;
        }

        public <A> Segment<A, S, T> returns(String expression, Class<A> clazz) {
            return (Segment<A, S, T>) this;
        }

        public T done() {
            return parent;
        }

    }


    public class Op<U, S, T extends Base<S, ?, ?>> extends Base<U, S, T> {

        private Supplier<Step> step;

        private T parent;

        public Op(T parent) {
            this.parent = parent;
        }

        public <OUT> Op<OUT, OUT, T> op(Supplier<Step<U, OUT>> operation) {
            this.step = (Supplier) operation;
            return (Op<OUT, OUT, T>) this;
        }

        public <A> Op<A, S, T> returns(String expression, Class<A> clazz) {
            return (Op<A, S, T>) this;
        }

        public T done() {
            return parent;
        }

    }

    public interface Step<IN, OUT> {

        OUT execute(IN object);
        
    }

Little explanation of the generics :

  • U is the type of the entry
  • S is the type of the entry of the parent node
  • T is the parent

I did this because, when an operation finishes, i wanted the parent (Segment) to have its U generic (its entry) modified according to the OUT (U of the Op) of the operation.

This is visible in the Op class, with the (Op<OUT, OUT, T>) this cast. I wish the T generic would be impacted by this cast, because we are telling the current S generic of this Op to be the OUT type of the operation, and as the T generic depends on S, then it will work. But this does not work, T is fixed. :(

I tried to not returning T when we call the done() method, but returns the definition of T, like this :

        public Segment<S, ?, ?> done() {
            return parent;
        }

This way the problem is resolved but i'm losing all the informations about the ancestors and these are mandatory to make the full pipeline consistent.

Do you have an idea on how can i resolve this problem keeping the recursive generics ?

Thanks in advance

0 Answers
Related