All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diffplug.common.base.Errors Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/*
 * Copyright 2015 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.io.PrintWriter;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/** 
 * Executes code and wraps functions, sending any errors to a {@code Consumer} error handler,
 * see ErrorsExample.
 */
public abstract class Errors implements Consumer {
	/** Package-private for testing - resets all of the static member variables. */
	static void resetForTesting() {
		log = null;
		dialog = null;
	}

	protected final Consumer handler;

	protected Errors(Consumer error) {
		this.handler = error;
	}

	/**
	 * Creates an Errors.Handling which passes any exceptions it receives
	 * to the given handler.
	 * 

* The handler is free to throw a RuntimeException if it wants to. If it always * throws a RuntimeException, then you should instead create an Errors.Rethrowing * using {@link #createRethrowing}. */ public static Handling createHandling(Consumer handler) { return new Handling(handler); } /** * Creates an Errors.Rethrowing which transforms any exceptions it receives into a RuntimeException * as specified by the given function, and then throws that RuntimeException. *

* If that function happens to throw an unchecked error itself, that'll work just fine too. */ public static Rethrowing createRethrowing(Function transform) { return new Rethrowing(transform); } /** Suppresses errors entirely. */ public static Handling suppress() { return suppress; } private static final Handling suppress = createHandling(Consumers.doNothing()); /** Rethrows any exceptions as runtime exceptions. */ public static Rethrowing rethrow() { return rethrow; } private static final Rethrowing rethrow = createRethrowing(Errors::asRuntime); /** * Logs any exceptions. *

* By default, log() calls Throwable.printStackTrace(). To modify this behavior * in your application, call DurianPlugins.set(Errors.Plugins.Log.class, error -> myCustomLog(error)); * * @see DurianPlugins * @see Errors.Plugins.OnErrorThrowAssertion */ @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "This race condition is fine, as explained in the comment below.") public static Handling log() { if (log == null) { // There is an acceptable race condition here - log might get set multiple times. // This would happen if multiple threads called log() at the same time // during initialization, and this is likely to actually happen in practice. // // Because DurianPlugins guarantees that its methods will have the exact same // return value for the duration of the library's runtime existence, the only // adverse symptom of this race condition is that there will temporarily be // multiple instances of Errors which are wrapping the same Consumer. // // It is important for this method to be fast, so it's better to accept // that suppress() might return different Errors instances which are wrapping // the same actual Consumer, rather than to incur the cost of some // type of synchronization. log = createHandling(DurianPlugins.get(Plugins.Log.class, Plugins::defaultLog)); } return log; } private static Handling log; /** * Opens a dialog to notify the user of any exceptions. It should be used in cases where * an error is too severe to be silently logged. *

* By default, dialog() opens a JOptionPane. To modify this behavior in your application, * call DurianPlugins.set(Errors.Plugins.Dialog.class, error -> openMyDialog(error)); *

* For a non-interactive console application, a good implementation of would probably * print the error and call System.exit(). * * @see DurianPlugins * @see Errors.Plugins.OnErrorThrowAssertion */ @SuppressFBWarnings(value = "LI_LAZY_INIT_STATIC", justification = "This race condition is fine, as explained in the comment below.") public static Handling dialog() { if (dialog == null) { // There is an acceptable race condition here. See Errors.log() for details. dialog = createHandling(DurianPlugins.get(Plugins.Dialog.class, Plugins::defaultDialog)); } return dialog; } private static Handling dialog; /** Passes the given error to this Errors. */ @Override public void accept(Throwable error) { handler.accept(error); } /** Attempts to run the given runnable. */ public void run(Throwing.Runnable runnable) { wrap(runnable).run(); } /** Returns a Runnable whose exceptions are handled by this Errors. */ public Runnable wrap(Throwing.Runnable runnable) { return () -> { try { runnable.run(); } catch (Throwable e) { handler.accept(e); } }; } /** Returns a Consumer whose exceptions are handled by this Errors. */ public Consumer wrap(Throwing.Consumer consumer) { return val -> { try { consumer.accept(val); } catch (Throwable e) { handler.accept(e); } }; } /** * An {@link Errors} which is free to rethrow the exception, but it might not. *

* If we want to wrap a method with a return value, since the handler might * not throw an exception, we need a default value to return. */ public static class Handling extends Errors { protected Handling(Consumer error) { super(error); } /** Attempts to call {@code supplier} and returns {@code onFailure} if an exception is thrown. */ public T getWithDefault(Throwing.Supplier supplier, T onFailure) { return wrapWithDefault(supplier, onFailure).get(); } /** Returns a Supplier which wraps {@code supplier} and returns {@code onFailure} if an exception is thrown. */ public Supplier wrapWithDefault(Throwing.Supplier supplier, T onFailure) { return () -> { try { return supplier.get(); } catch (Throwable e) { handler.accept(e); return onFailure; } }; } /** Returns a Function which wraps {@code function} and returns {@code onFailure} if an exception is thrown. */ public Function wrapWithDefault(Throwing.Function function, R onFailure) { return input -> { try { return function.apply(input); } catch (Throwable e) { handler.accept(e); return onFailure; } }; } /** Returns a Predicate which wraps {@code predicate} and returns {@code onFailure} if an exception is thrown. */ public Predicate wrapWithDefault(Throwing.Predicate predicate, boolean onFailure) { return input -> { try { return predicate.test(input); } catch (Throwable e) { handler.accept(e); return onFailure; } }; } } /** * An {@link Errors} which is guaranteed to always throw a RuntimeException. *

* If we want to wrap a method with a return value, it's pointless to specify * a default value because if the wrapped method fails, a RuntimeException is * guaranteed to throw. */ public static class Rethrowing extends Errors { private final Function transform; protected Rethrowing(Function transform) { super(error -> { throw transform.apply(error); }); this.transform = transform; } /** Attempts to call {@code supplier} and rethrows any exceptions as unchecked exceptions. */ public T get(Throwing.Supplier supplier) { return wrap(supplier).get(); } /** Returns a Supplier which wraps {@code supplier} and rethrows any exceptions as unchecked exceptions. */ public Supplier wrap(Throwing.Supplier supplier) { return () -> { try { return supplier.get(); } catch (Throwable e) { throw transform.apply(e); } }; } /** Returns a Function which wraps {@code function} and rethrows any exceptions as unchecked exceptions. */ public Function wrap(Throwing.Function function) { return arg -> { try { return function.apply(arg); } catch (Throwable e) { throw transform.apply(e); } }; } /** Returns a Predicate which wraps {@code predicate} and rethrows any exceptions as unchecked exceptions. */ public Predicate wrap(Throwing.Predicate predicate) { return arg -> { try { return predicate.test(arg); } catch (Throwable e) { throw transform.apply(e); // 1 855 548 2505 } }; } } /** Casts or wraps the given exception to be a RuntimeException. */ public static RuntimeException asRuntime(Throwable e) { if (e instanceof RuntimeException) { return (RuntimeException) e; } else { return new WrappedAsRuntimeException(e); } } /** A RuntimeException specifically for the purpose of wrapping non-runtime Throwables as RuntimeExceptions. */ public static class WrappedAsRuntimeException extends RuntimeException { private static final long serialVersionUID = -912202209702586994L; public WrappedAsRuntimeException(Throwable e) { super(e); } } /** Namespace for the plugins which Errors supports. */ public interface Plugins { /** Plugin interface for {@link Errors#log}. */ public interface Log extends Consumer {} /** Plugin interface for {@link Errors#dialog}. */ public interface Dialog extends Consumer {} /** Default behavior of {@link Errors#log} is @{code Throwable.printStackTrace()}. */ static void defaultLog(Throwable error) { error.printStackTrace(); } /** Default behavior of {@link Errors#dialog} is @{code JOptionPane.showMessageDialog} without a parent. */ static void defaultDialog(Throwable error) { SwingUtilities.invokeLater(() -> { error.printStackTrace(); String title = error.getClass().getSimpleName(); JOptionPane.showMessageDialog(null, error.getMessage() + "\n\n" + StringPrinter.buildString(printer -> { PrintWriter writer = printer.toPrintWriter(); error.printStackTrace(writer); writer.close(); }), title, JOptionPane.ERROR_MESSAGE); }); } /** * An implementation of all of the {@link Errors} plugins which throws an AssertionError * on any exception. This can be helpful for JUnit tests. *

* To enable this in your application, you can either: *

    *
  • Execute this code at the very beginning of your application:
    		 * DurianPlugins.set(Errors.Plugins.Log.class, new OnErrorThrowAssertion());
    		 * DurianPlugins.set(Errors.Plugins.Dialog.class, new OnErrorThrowAssertion());
    		 * 
  • *
  • Set these system properties:
    		 * durian.plugins.com.diffplug.common.base.Errors.Plugins.Log=com.diffplug.common.base.Errors$Plugins$OnErrorThrowAssertion
    		 * durian.plugins.com.diffplug.common.base.Errors.Plugins.Dialog=com.diffplug.common.base.Errors$Plugins$OnErrorThrowAssertion
    		 * 
  • *
* * @see DurianPlugins */ public static class OnErrorThrowAssertion implements Log, Dialog { @Override public void accept(Throwable error) { throw new AssertionError(error); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy