In the case of functions, when exceptions may only throw at their place of execution (based on my lack of knowledge this is uncertain for me at the moment), sometimes the culprit may rely on the way the lambda was created, an example may be:
// merge() will throw an exception if Function<S, Observable<T>> switchMap returns null.
public<S> void mergeSwitch(Observable<S> source, Function<S, Observable<T>> switchMap) {
final Mergeable<T> res = new Mergeable<>();
final Consumer<T> observer = creator(res, this); // Secondary subscription
setSource(
source, // Main subscription
new StateAwareObserver<S>() { // >>> implements Consumer<S>
Observable<T> switchRes = new Observable<>(); // Mediator
@Override
protected void onAdded() {
res.add(observer);
}
@Override
protected void onRemoved() {
res.remove(observer);
}
@Override
public void accept(S s) {
Observable<T> switchS = switchMap.apply(s);
if (switchRes != switchS) {
switchRes = switchS;
res.merge(switchS); //Where merge() throws if switchMap returns null
}
}
}
);
}
}
I've tried rethrowing the exception from the accept() method but it never gets pushed(pulled?) beyond its point of consumption, (where the consumer accepts "s"):
for (int i = observers.size() - 1; i >=0 ; i--) {
observers.get(i).accept(processed); // << -- StateAwareObserver<S> is stored in the *list*
}
-In this case I am using the word "beyond", but what I need is to throw the exception at its point of construction.-
In reality the real culprit may have been that the execution of the method: mergeSwitch(Observable<S> source, Function<S, Observable<T>> switchMap)
which created the Consumer<>, may have left something important out.
Or as is my case, the method executing mergeSwitch():
public<S> Forkable<S> forkSwitch(Function<T, Observable<S>> switchMap) {
Forkable<S> res = new Forkable<>();
res.mergeSwitch(this, switchMap);
return res;
}
In this case the issue was that the function was left with a null value.
public Holders.Forkable<List<Preset>> ledgerPresets = presetDao.forkSwitch(
presetDao -> ledgerId.forkSwitch(
aLong -> null
)
);
So if we take a look at the Consumer level, the real culprit would be:
@Override
public void accept(S s) {
int sVersion = source.getVersion();
if (sVersion != oVersion) {
Observable3<T> switchS = switchMap.apply(s); //< -- HERE
if (switchRes != switchS) {
switchRes = switchS;
res.merge(switchS);
}
oVersion = sVersion;
}
}
In this particular scenario, things can get out of hand easily since, the log may not stop at its nearest point of consumption, but the error may go all the way back to the root node.
Things can get even more complicated since the merge() method, which is the origin of the exception, may have been used from 3 different methods.
The ideal scenario would be that the exception would be thrown here:
public Holders.Forkable<List<Preset>> ledgerPresets = presetDao.forkSwitch(
presetDao -> ledgerId.forkSwitch( // <--- HERE
aLong -> null
)
);
But I haven't found a way to manage this particular exception rethrowing case yet.
EDIT:
In my previous answer I spent so much time trying to push the exception a stacktrace level, that I simply stopped trying to do it the easy way, you can ignore the craziness of the answer bellow the new one:
BEST ANSWER
OLD ANSWER
I tried searching for an answer but did not came up with an ideal one.
My solution has been capturing an int storing the top stacktrace and then infering the class name via the lambda.
The reason why I find this solution to be lackluster is that the origin of the exception is actually not used, what we are really doing is intercepting the event before it actually happens, but imo this brings redundancy.
fortunately the exception is now more clear than before:
I believe one could encapsulate the entire process in a neat little object that creates the stacktrace before entering a lambda, and then creates an exception on command when required inside the lambda itself.
Here is a helper object:
Usage: