At @amon's suggestion, here's an answer that's more monadic. It's a very boiled down version, where you have to accept a few assumptions:
the "unit" or "return" function is the class constructor
the "bind" operation happens at compile time, so it's hidden from the invocation
the "action" functions are also bound to the class at compile time
although the class is generic and wraps any arbitrary class E, I think that's actually overkill in this case. But I left it that way as an example of what you could do.
With those considerations, the monad translates into a fluent wrapper class (although you're giving up a lot of the flexibility that you'd get in a purely functional language):
public class RepositoryLookup<E> {
private String source;
private E answer;
private Exception exception;
public RepositoryLookup<E>(String source) {
this.source = source;
}
public RepositoryLookup<E> fetchElement() {
if (answer != null) return this;
if (! exception instanceOf NotFoundException) return this;
try {
answer = lookup(source);
}
catch (Exception e) {
exception = e;
}
return this;
}
public RepositoryLookup<E> orFetchSimilarElement() {
if (answer != null) return this;
if (! exception instanceOf NotFoundException) return this;
try {
answer = lookupVariation(source);
}
catch (Exception e) {
exception = e;
}
return this;
}
public RepositoryLookup<E> orFetchParentElement() {
if (answer != null) return this;
if (! exception instanceOf NotFoundException) return this;
try {
answer = lookupParent(source);
}
catch (Exception e) {
exception = e;
}
return this;
}
public boolean failed() {
return exception != null;
}
public Exception getException() {
return exception;
}
public E getAnswer() {
// better to check failed() explicitly ;)
if (this.exception != null) {
throw new IllegalArgumentException(exception);
}
// TODO: add a null check here?
return answer;
}
}
(this won't compile... certain details are left unfinished to keep the sample small)
And the invocation would look like this:
Repository<String> repository = new Repository<String>(x);
repository.fetchElement().orFetchParentElement().orFetchSimilarElement();
if (repository.failed()) {
throw new IllegalArgumentException(repository.getException());
}
System.err.println("Got " + repository.getAnswer());
Note that you have the flexibility to compose the "fetch" operations as you like. It will stop when it gets an answer or an exception other than not found.
I did this really fast; it's not quite right, but hopefully conveys the idea