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