Need to find return type method's lambda function parameter

80 Views Asked by At

(Note.. I have restructured the example to more closely match the use case I am working on)

I have the following interface defined:

public interface Interceptor {
    <T> T intercept (Invocation<T> invocation);

    interface Invocation<T> {
        T proceed();
    }
}

This simplest implementation of this would be something like:

class TestInterceptor {
    static class Line {int x1, y1, z1, x2, y2, z2;}
    static class Color {int r, g, b;}
    static class Renderer {
        Interceptor interceptor;
        Renderer (Interceptor interceptor) {this.interceptor = interceptor;}
        void draw (Supplier<Line> lineSource, Supplier<Color> colorSource) {
            Line thisLine = interceptor.intercept(lineSource::get);
            Color thisColor = interceptor.intercept(colorSource::get);
            // do the drawing
        }
    }
    void drawSomething () {
        Renderer render = new Renderer(getInterceptor());
        render.draw( () -> createLine(), () -> createColor() );
    }
    Line createLine () {return new Line(/*something*/);}
    Color createColor () {return new Color(/*something*/);}
    Interceptor getInterceptor () {
        return new Interceptor() {
            @Override
            <T> T intercept (Invocation<T> invocation) {
                /* would like to do this:
                if (invocation.returnType = Line) {
                   return new MyLIne();
                } else {
                   return invocation.proceed();
                } */
               return invocation.proceed();
             }
         }
     }
}

Notice the comment in getInterceptor() that I would like to take different actions (including not calling the lambda) based on what the lambda is going to return. I have tried everything I can think of to get the actual type behind the parameterized type T but keep drawing a blank. Can anyone help me out here?

Here are some of the things I've tried

Class<?> wclass = interceptor.getClass();
Method method = wclass.getMethod("proceed", new Class<?>[]{});
Class<?> rtype = method.getReturnType(); //getName() always returns "java.lang.Object"
Type gtype = method.getGenericReturnType();
Class<?> gtclass = gtype.getClass(); //getName() always returns "java.lang.Class"
String gtname = gtype.getTypeName()); //always returns "java.lang.Object"

I am actually trying to create a "Remote Launcher" for JUnit5 so I can use Maven SureFire to run tests in a separatly launched and configured OSGi "container" (way overloaded term but don't get me started). Except for the getInterceptor() method, all of the sample code above is a simplification of existing Junit5 code and not anything I can change.

(Ideally, I would integrate the new Remote Launcher into PaxExam which seems to have stagnated.)

2

There are 2 best solutions below

1
CausingUnderflowsEverywhere On

You can make the interface use a type parameter just like how Lists work in Java.

interface WorkValidator<T> {
    T checkWork (Worker<T> worker);

    interface Worker<T> {
        T doWork();
    }
}

Then your implementations can be specific implementations such as a string worker like so:

class StringWorkValidator implements WorkValidator<String> {
    @Override 
    public String checkWork (Worker<String> worker) {
        return worker.doWork();
    }
}
. . .
StringWorkValidator myValidator = new StringWorkValidator();
. . .
String finishedWork = myValidator.checkWork( () -> someWorker.work(p1, p2, etc) );
1
William Denton On

Thankyou everyone for providing great feedback on my question. I am sorry if I was not completely clear in details of my problem.

VGR's comments corroborated what I was afraid of... that there isn't yet any way to do exactly what I have hoped for. Maybe a future Java release will allow this sort of deep reflection at runtime.

Anyway, the best solution I could come up with was to go ahead and call invocation.proceed() then use old-school Class.isAssignableFrom() to examine its result. Then return a replacement object instead. This would look like this...

return new Interceptor() {
    @Override
    <T> T intercept (Invocation<T> invocation) {
        T result = invocation.proceed();
        if (result != null && Line.class.isAssignableFrom(result.getClass()) {
            result = new MyLine((Line) result); // maybe MyLine needs to delegate actions
        }
        return result;
    };

Of course, this assumes there aren't any undesirable side effects of calling the lambda but replacing its results with your own.