/*
* Copyright 2016 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.common.base;
import java.util.Arrays;
import java.util.List;
import com.diffplug.common.base.Errors;
import com.diffplug.common.base.Throwing;
// @formatter:off
@SuppressWarnings({"serial"})
public class ErrorsExample {
/** Errors is a simple little class that makes ErrorHandling a lot easier. */
public void whyItsGreat() throws Exception {
List<Food> foodOnPlate = Arrays.asList(
cook("salmon"),
cook("asparagus"),
cook("enterotoxin"));
// without Errors, we have to write this
foodOnPlate.forEach(val -> {
try {
eat(val);
} catch (Barf e) {
// get out the baking soda
}
});
// With Errors, we can succinctly
// sweep it under the rug
foodOnPlate.forEach(Errors.suppress().wrap(this::eat));
// save it for later
foodOnPlate.forEach(Errors.log().wrap(this::eat));
// make mom deal with it
foodOnPlate.forEach(Errors.rethrow().wrap(this::eat));
// ask the user deal with it
foodOnPlate.forEach(Errors.dialog().wrap(this::eat));
// We can also make our own Errorses, which can be reused across a project
Errors retryHandler = Errors.createHandling(error -> {
if (error instanceof Barf) {
Food cause = ((Barf) error).looksLikeItUsedToBe();
try {
eat(cause);
} catch (Barf barf) {
// at least we tried really hard
}
}
});
foodOnPlate.forEach(retryHandler.wrap(this::eat));
}
public void whereDoLogAndDialogComeFrom() {
// Errors.log() promises to "log", and Errors.dialog() promises to alert the user.
// Doing those things is very different depending on whether your code is running as a web
// application, console application, or desktop gui
// One way around this ambiguity is to avoid using Errors.log() and Errors.dialog()
// entirely, and only use your own custom Errors. But if you are shipping a framework,
// and your users might end up using Errors.log() and .dialog(), then you might want to
// specify what those do.
// By default, Errors.log() is just Throwable.printStackTrace, and Errors.dialog()
// opens a JOptionPane. You can modify this behavior with the following:
DurianPlugins.register(Errors.Plugins.Log.class, error -> {
// log to twitter
});
DurianPlugins.register(Errors.Plugins.Dialog.class, error -> {
// Headless application: email the sysadmin and exit
// Web application: ajax an alert() to the user
});
// The trick is, you have to call these methods BEFORE Errors.log() or Errors.dialog()
// are used anywhere in your whole application. Once log() or dialog() have been used, they are
// fixed for the duration of the runtime. If you're writing a library, then you probably shouldn't
// try to change them. If you're writing an application or framework, then you probably should.
// If you're running in a JUnit environment, then you probably want any call to log() or dialog()
// to kill the test. Setting the following system properties (again, before log() or dialog() are
// called) will cause any errors to get wrapped and thrown as a java.lang.AssertionError.
System.setProperty("durian.plugins.com.diffplug.common.base.Errors.Plugins.Log",
"com.diffplug.common.base.Errors$Plugins$OnErrorThrowAssertion");
System.setProperty("durian.plugins.com.diffplug.common.base.Errors.Plugins.Dialog",
"com.diffplug.common.base.Errors$Plugins$OnErrorThrowAssertion");
}
@Override
public Object clone() {
// We'd like to return a Food,
// but the only way to get it is the cook() method,
// which throws a checked exception
try {
return cook("spaghetti");
} catch (IAmOnFire e) {
// TODO: water()
return CEREAL;
}
// Our superclass doesn't let us propagate the exception,
// so we've got to either
// A) return a default value
// B) rethrow the checked exception, wrapped in a RuntimeException
//
// If we decide to take the "default value" route, we might want to suppress
// the memory of being on fire, or we might want to log it to twitter
}
@Override
public void finalize() {
// If we're taking the "default value" route, Errors has you covered
Food logged = Errors.log().getWithDefault(() -> cook("spaghetti"), CEREAL); // log to twitter
Food suppressed = Errors.suppress().getWithDefault(() -> cook("spaghetti"), CEREAL); // suppress to insecurity buffer
// If we're taking the "rethrow RuntimeException" route, then specifying a
// default value would be nonsensical, so we don't do it
Food rethrow = Errors.rethrow().get(() -> cook("spaghetti"));
// I'm stressed, don't judge me
Errors.suppress().run(() -> {
eat(logged);
eat(suppressed);
eat(rethrow);
});
}
public void finalizeAdvanced() {
// The previous example uncovered one of Errors's secrets. There are two
// subclasses of Errors: Errors.Handling, and Errors.Rethrowing.
// An instance of Errors.Rethrowing is an Errors that guarantees by construction
// to always handle errors by throwing a RuntimeException. This means that when it is returning
// a value from a fallible function, it doesn't need a default value.
Errors.Rethrowing rethrowing = Errors.createRethrowing(error -> {
// Note that we're returning the exception, not throwing it.
// The Errors will throw it for us, thus "guaranteed by construction".
// It'd be okay if we threw it ourselves too, but it's not as pretty.
return new RuntimeException("AHHHHHHHHHHHHHHHHHH!!!!");
});
Food thrown = rethrowing.get(() -> cook("spaghetti")); // no default needed
// An instance of Errors.Handling is an Errors that doesn't make this guarantee.
// Since it isn't going to handle errors by throwing an exception (well, it might, but it isn't
// promising to, so it might not), a default value is required.
Errors.Handling handling = Errors.createHandling(error -> {
if (error instanceof RuntimeException) {
throw (RuntimeException) error;
} else {
System.err.println("Hot! Hot! Hot! Hot!");
}
});
Food handled = handling.getWithDefault(() -> cook("spaghetti"), CEREAL); // gotta have that default
// A plain-old Errors can deal with functions that don't return values (namely Runnable and Consumer).
// But to work with functions that do return a value (namely Supplier and Function), you're gonna
// need to have it in its true Handling / Rethrowing form. In a modern IDE with autocomplete,
// The Right Thing will just automatically happen for you.
if (thrown.equals(handled)) {
// I guess I wasted my time with the two subclasses, because it didn't matter in the end.
} else {
// Time well spent.
}
}
@SuppressWarnings("unused")
public void wrapping() {
// If your functional interface has 0 or 1 outputs, and 0 or 1 inputs, then Errors can wrap it into its standard Java 8 form
Throwing.Runnable marathon = () -> { throw new IAmOnFire(); };
java.lang.Runnable marathonSafe = Errors.log().wrap(marathon);
Throwing.Consumer<Food> eat = this::eat;
java.util.function.Consumer<Food> eatSafe = Errors.log().wrap(eat);
Throwing.Supplier<Food> cookSpatula = () -> cook("spatula");
java.util.function.Supplier<Food> cookSpatulaOrGetCereal = Errors.log().wrapWithDefault(cookSpatula, CEREAL);
java.util.function.Supplier<Food> cookSpatulaOrBurn = Errors.rethrow().wrap(cookSpatula);
Throwing.Function<String, Food> cookAnything = this::cook;
java.util.function.Function<String, Food> cookAnythingOrGetCereal = Errors.log().wrapWithDefault(this::cook, CEREAL);
java.util.function.Function<String, Food> cookAnythingOrBurn = Errors.rethrow().wrap(this::cook);
// If your function has more than 1 input, you can either
// A) Make a wrapper function that calls Errors.get() to return a value (recommended)
// B) Make a "wrapper wrapper" (see https://github.com/diffplug/durian/blob/master/test/com/diffplug/common/base/ErrorsMultipleInputs.png)
// If your function has more than 1 output, see https://github.com/diffplug/durian/blob/master/test/com/diffplug/common/base/ErrorsMultipleOutputs.png
}
@SuppressWarnings("unused")
public void throwingSpecfic() {
// Throwing.Specific lets you express functional interfaces which throw a specific exception
Throwing.Specific.Consumer<Food, Barf> eatSignature = this::eat;
Throwing.Specific.Function<String, Food, IAmOnFire> cookSignature = this::cook;
// This can be helpful for writing generic code for working with a specific kind of exception, but
// it isn't helpful for writing code for generic exceptions (because type erasure).
//
// The only way to know the type of List<T> is to grab an element out of the
// list and call getClass() on it. And then you still mostly don't know the type of the list.
// This same limitation ripples through exception handling in the following way:
class BarfHarness {
// If you knew it was Barf at compile time then you can catch it
void exceptionKnownAtCompileTime(Throwing.Specific.Runnable<Barf> eatAndThen) {
try {
eatAndThen.run();
} catch (Barf e) {
// and on Janitor's day too!
}
}
// If the exception was generic at compile time, then you have to catch the most-general possible exception, which is Throwable
<E extends Throwable> void exceptionGenericAtCompileTime(Throwing.Specific.Runnable<E> eatAndThen) {
try {
eatAndThen.run();
// You can try "catch (E e)", but you'll get: "Cannot use the type parameter E in a catch block"
// You can try "catch (Barf e)", but you'll get: "Unreachable catch block for ErrorsExample.Barf. This exception is never thrown from the try statement body"
} catch (Throwable e) {
// Looks like we're stuck catching Throwable. Why should we bother having the exception be generic?
}
}
}
// Well, we probably shouldn't. It didn't do us any good.
// Throwing.Specific.* is most useful when it's implementing plain-old Throwing.*
Throwing.Runnable runnableNonSpecific = () -> { throw new IAmOnFire(); };
Throwing.Specific.Runnable<Throwable> runnable = runnableNonSpecific;
// Here's the voluminous source code for Throwing:
// public interface Runnable extends Specific.Runnable<Throwable> {}
// public interface Supplier<T> extends Specific.Supplier<T, Throwable> {}
// public interface Consumer<T> extends Specific.Consumer<T, Throwable> {}
// public interface Function<T, R> extends Specific.Function<T, R, Throwable> {}
// public interface Predicate<T> extends Specific.Predicate<T, Throwable> {}
// Now you know how to cook spaghetti and eat salmon using Java >= 8 and generic exceptions using Errors!
}
/** Immutable, unfortunately. */
private class IAmOnFire extends Exception {}
/** A monoid over the group "food". */
class Food {}
/** Needs another day or two to flesh out some implementation details. */
private class Barf extends Exception {
public Food looksLikeItUsedToBe() {
// TODO: Computer vision
return null;
}
}
/** Endofunctor of the monoid over the food. */
void eat(Food food) throws Barf {}
/** Returns the Hamming distance between the ingredients. */
Food cook(String ingredients) throws IAmOnFire {
return null;
}
/** This is obtained after stamping the natural numbers in ascending order on individual grains. */
private static final Food CEREAL = null;
}
//@formatter:on