5

I have a design problem in a common utility that we use in our Java project where I want to ensure that all callers of a particular method A are wrapped by another method B. The general form of this code as I've written it today is:

x.B(new Runnable() {
    y.A();
});

The runnable that is being executed by B can have arbitrary code, and may call A multiple times, so I can't get rid of the runnable in this code by adding the call to A directly into B. Also, A is third-party code, so we can't modify it. It's possible that the runnable could call B again with another nested call to A, but today that never happens so I am ok ignoring that case for now.

I see a few options:

  1. Declare A() throws BlahException and make it so that B is the only catcher of that exception. This is ugly because there isn't any exception that should truly be thrown, but it's nice because the compiler would ensure the call hierarchy for me.
  2. Write some sort of static analysis tool to ensure this rule for me. I haven't investigated this case much yet, as it sounds like more work than anything else (but maybe there is a preexisting tool that can do this?).
  3. Add an assertion to the "beginning of A" (really, this code would have to live in a custom version of Runnble since I can't modify A directly) that we are running inside of a call to B. This could either use some additional thread-/object-local state or traverse the call stack itself, both of which are kind of ugly.

Are there other options I haven't considered?

Dan
  • 7,155
  • 2
  • 29
  • 54
  • 1
    This is very much an anti-pattern. Method `A` should not care who called it. Why do you think you need to do this? – Kevin Krumwiede May 26 '15 at 18:55
  • 1
    Did you try to use Proxy pattern? – MaxZoom May 26 '15 at 18:58
  • @KevinKrumwiede - It is possible for both `A` to be agnostic of its caller and the project to want all calls to `A` to be wrapped. – Andy Thomas May 26 '15 at 19:03
  • @gerty3000 - Other options: 1) document the requirement and perform code reviews, and 2) aspect-oriented programming. The former is lightweight but not guaranteed. The latter seems fairly heavyweight. – Andy Thomas May 26 '15 at 19:06
  • @KevinKrumwiede: Agreed; this is working around a bug in third-party code (specifically, JNA callbacks from native code back into Java) which eventually results in native heap exhaustion, which our wrapper will prevent. If the abstraction was not leaky, we wouldn't care who was calling it. – Dan May 27 '15 at 17:38

1 Answers1

2

Have you considered using AspectJ, or some other aspect-oriented programming (AOP) tools? Then you could intercept every call of method A, check for method B in the stacktrace. If it's not there, you could throw exception preventing execution of A, or write an error to log, or do whatever you like. Something like this:

@Aspect
public class CheckInsideMethodBAspect {

    @Around("execution(* com.example.AClass.A(..))")
    public void checkNestedMethod(ProceedingJoinPoint joinPoint) {

        // checking for method B in the call stack
        boolean isInsideB = false;
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        for (StackTraceElement element: stackTraceElements) {
            if (element.getClassName().equals("ClassB") && element.getMethodName().equals("B")) {
                isInsideB = true;
                break;
            }
        }

        // if not inside B, throwing exception
        if (!isInsideB) {
            throw new NotInsideBException();
        }

        // if inside B, then proceeding with method A execution
        joinPoint.proceed();
    }

}
Forketyfork
  • 7,416
  • 1
  • 26
  • 33
  • 1
    Whatever the reason behind the requirement that method `B` is in the call stack - i.e., whatever method `B` does that is so important - can probably be achieved directly with this approach, leaving `B` out of it. – Kevin Krumwiede May 26 '15 at 19:19