Martin Fowler posted Replacing Throwing Exceptions with Notification in Validations discussing alternatives to data validation than throwing exceptions. There are off-the-shelf solutions such as Commons Validator (XML driven) or Bean Validation (annotation driven) which are complete frameworks.
There is more to these frameworks than I suggest, but to explore Fowler's post better I quickly coded up my own simple-minded approach:
public final class ValidationMain {
public static void main(final String... args) {
final Notices notices = new Notices();
notices.add("Something went horribly wrong %d time(s)", 1);
try {
foo(null);
} catch (final Exception e) {
notices.add(e);
}
notices.forEach(err::println);
notices.fail(IllegalArgumentException::new);
}
public static String foo(final Object missing) {
return missing.toString();
}
} Output on stderr:
lab.Notices$Notice@27f8302d lab.Notices$Notice@4d76f3f8 Exception in thread "main" java.lang.IllegalArgumentException: 2 notices: Something went horribly wrong 1 time(s) at lab.ValidationMain.main(ValidationMain.java:21) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) Suppressed: java.lang.IllegalArgumentException: Only 1 reason(s) at lab.ValidationMain.main(ValidationMain.java:14) Suppressed: java.lang.IllegalArgumentException at lab.ValidationMain.main(ValidationMain.java:18) Caused by: java.lang.NullPointerException at lab.ValidationMain.foo(ValidationMain.java:25) at lab.ValidationMain.main(ValidationMain.java:16)
And the Notices class:
public final class Notices
implements Iterable<Notice> {
private final List<Notice> notices = new ArrayList<>(0);
public void add(final String reason, final Object... args) {
// Duplicate code so stack trace keeps same structure
notices.add(new Notice(null, reason, args));
}
public void add(final Throwable cause) {
// Duplicate code so stack trace keeps same structure
notices.add(
new Notice(cause, null == cause ? null : cause.getMessage()));
}
public void add(final Throwable cause, final String reason,
final Object... args) {
// Duplicate code so stack trace keeps same structure
notices.add(new Notice(cause, reason, args));
}
public <E extends Exception> void fail(
final BiFunction<String, Throwable, E> ctor)
throws E {
final E e = ctor.apply(toString(), null);
notices.forEach(n -> e.addSuppressed(n.as(ctor)));
final List<StackTraceElement> frames = asList(e.getStackTrace());
// 2 is the magic number: lambda, current
e.setStackTrace(frames.subList(2, frames.size())
.toArray(new StackTraceElement[frames.size() - 2]));
throw e;
}
@Override
public Iterator<Notice> iterator() {
return unmodifiableList(notices).iterator();
}
@Override
public String toString() {
if (notices.isEmpty())
return "0 notices";
final String sep = lineSeparator() + "\t";
return notices.stream().
map(Notice::reason).
filter(Objects::nonNull).
collect(joining(sep,
format("%d notices:" + sep, notices.size()), ""));
}
public static final class Notice {
// 4 is the magic number: Thread, init, init, addError, finally the
// user code
private final StackTraceElement location = currentThread()
.getStackTrace()[4];
private final Throwable cause;
private final String reason;
private final Object[] args;
private Notice(final Throwable cause, final String reason,
final Object... args) {
this.cause = cause;
this.reason = reason;
this.args = args;
}
public Throwable cause() {
return cause;
}
public String reason() {
return null == reason ? null : format(reason, args);
}
private <E extends Exception> E as(
final BiFunction<String, Throwable, E> ctor) {
final E e = ctor.apply(reason(), cause);
e.setStackTrace(new StackTraceElement[]{location});
return e;
}
}
} Comments:
- I manipulate the stack traces to focus on the caller's point of view. This is the opposite of, say, Spring Framework.
- I haven't decided on what an intelligent
toString()should look like forNotice - Java 8 lambdas really shine here. Being able to use exception constructors as method references is a win.
No comments:
Post a Comment