/* * 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