/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.util.result; import java.util.ArrayList; import java.util.List; import org.slf4j.helpers.MessageFormatter; import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; import com.opengamma.util.ArgumentChecker; /** * The immutable result from a calculation containing an indication * of whether a value has been calculated. * Results can be generated using the factory methods on this class. * * @param <T> the type of the underlying result for a successful invocation */ public abstract class Result<T> { /** * Indicates if this result represents a successful call and has a result available. * * @return true if the result represents a success and a value is available */ public abstract boolean isSuccess(); /** * Return the actual result value if calculated successfully. * <p> * If it has not been calculated then an IllegalStateException will be thrown. * To avoid this, check the result status using {@link #isSuccess()} or * {@link #getStatus()} first. * * @return the value if calculated successfully, not null * @throws IllegalArgumentException if called when the result has not been successfully calculated */ public abstract T getValue(); /** * Indicates the status of this result. * <p> * It is up to the client to decide if it is able to handle the status or * decline to handle. In general it is easier to call {@link #isSuccess()}. * * @return the status of this function result */ public abstract ResultStatus getStatus(); /** * Return the message associated with a failure event. * <p> * If the calculation was actually successful then an an IllegalStateException will be thrown. * To avoid this, check the result status using {@link #isSuccess()} * or {@link #getStatus()} first. * * @return the failure message if calculation was unsuccessful, not null * @throws IllegalStateException if called on a success result */ public abstract String getFailureMessage(); /** * Gets the collection of failure instances that are associated with this result. * <p> * If the calculation was actually successful then an an IllegalStateException will be thrown. * To avoid this, check the result status using {@link #isSuccess()} * or {@link #getStatus()} first. * * @return the failures associated with a failure result, empty if successful */ public abstract ImmutableSet<Failure> getFailures(); /** * Applies a function to a result's value if the result is a success. * If the result is a failure then a failure is returned without applying the function. * Useful for applying logic to a successful result using Java 8 lambdas without having to check the status. * <pre> * result = ... * return result.ifSuccess(value -> doSomething(value)); * </pre> * Identical to {@link #flatMap} * * @param <U> the required type of the new result object * @param function the function to transform the value with, not null * @return the new result, not null */ public abstract <U> Result<U> ifSuccess(Function<T, Result<U>> function); /** * Applies a function to a result's value if the result is a success. * If the result is a failure then a failure is returned without applying the function. * Useful for applying logic to a successful result using Java 8 lambdas without having to check the status. * <pre> * result = ... * return result.flatMap(value -> doSomething(value)); * </pre> * Identical to {@link #ifSuccess} * * @param <U> the required type of the new result object * @param function the function to transform the value with, not null * @return the new result, not null */ public <U> Result<U> flatMap(Function<T, Result<U>> function) { return ifSuccess(function); } /** * Combines this result's value with another result's value using a binary function if both are successes. * If either result is a failure then a failure is returned without applying the function. * Useful for applying logic to successful results using Java 8 lambdas without having to check the statuses. * <pre> * result1 = ... * result2 = ... * return result1.combineWith(result2, (value1, value2) -> doSomething(value1, value2); * </pre> * * @param other another result * @param function a function for combining values from two results * @param <U> the type of the other result's value * @param <V> the type of the value in the returned result * @return a the result of combining the result values or a failure if either result is a failure */ public abstract <U, V> Result<V> combineWith(Result<U> other, Function2<T, U, Result<V>> function); //------------------------------------------------------------------------- /** * Indicates if there is a result value available from this instance. * <p> * This generally means that any calculation has been successfully performed * but for calculation that may return partial results e.g. market data * requests this method will return true. To distinguish between these * cases, check the result status using {@link #getStatus()}. * * @return true if a value is available * @deprecated use {@link #isSuccess()} */ @Deprecated public boolean isValueAvailable() { return isSuccess(); } /** * Propagate a failure result, ensuring that its generic type signature * matches the one required. * * @param <U> the required type of the new result object * @return the new function result object, not null * @throws IllegalStateException if invoked on a successful result * @deprecated use {@link #failure(Result)} */ @Deprecated public <U> Result<U> propagateFailure() { return Result.failure(this); } /** * Applies a function to a result's value if the result is a success. * If the result is a failure then a failure is returned without applying the function. * * @param <U> the required type of the new result object * @param function the mapper object to transform the value with, not null * @return the new result, not null * @deprecated use {@link #ifSuccess(Function)} or {@link #flatMap(Function)} */ @Deprecated public <U> Result<U> map(final ResultMapper<T, U> function) { return flatMap(new Function<T, Result<U>>() { @Override public Result<U> apply(T input) { return function.map(getValue()); } }); } //------------------------------------------------------------------------- /** * Creates a successful result wrapping a value * * @param value the result value * @param <U> the type of the value * @return a successful result wrapping the value */ public static <U> Result<U> success(U value) { return new SuccessResult<>(value); } /** * Creates a failed result. * <p> * Formatting of the error message uses placeholders as per SLF4J. * Each {} in the message is replaced by the next message argument. * * @param status the result status * @param message a message explaining the failure, uses the SLF4J message format for inserting {@code messageArgs} * @param messageArgs the arguments for the message * @param <U> the expected type of the result * @return a failure result */ public static <U> Result<U> failure(FailureStatus status, String message, Object... messageArgs) { return FailureResult.of(new Failure(status, formatMessage(message, messageArgs))); } /** * Creates a failed result caused by an exception. * <p> * Formatting of the error message uses placeholders as per SLF4J. * Each {} in the message is replaced by the next message argument. * * @param exception the cause of the failure * @param message a message explaining the failure, uses the SLF4J message format for inserting {@code messageArgs} * @param messageArgs the arguments for the message * @param <U> the expected type of the result * @return a failure result */ public static <U> Result<U> failure(Exception exception, String message, Object... messageArgs) { return FailureResult.of(new Failure(exception, formatMessage(message, messageArgs))); } /** * Creates a failed result caused by an exception. * * @param exception the cause of the failure * @param <U> the expected type of the result * @return a failure result */ public static <U> Result<U> failure(Exception exception) { return FailureResult.of(new Failure(exception)); } /** * Creates a failed result caused by an exception with a specified status. * * @param status the result status * @param exception the cause of the failure * @param <U> the expected type of the result * @return a failure result */ public static <U> Result<U> failure(FailureStatus status, Exception exception) { return FailureResult.of(new Failure(status, exception)); } /** * Creates a failed result caused by an exception with a specified status and message. * <p> * Formatting of the error message uses placeholders as per SLF4J. * Each {} in the message is replaced by the next message argument. * * @param status the result status * @param exception the cause of the failure * @param message a message explaining the failure, uses the SLF4J message format for inserting {@code messageArgs} * @param messageArgs the arguments for the message * @param <U> the expected type of the result * @return a failure result */ public static <U> Result<U> failure(FailureStatus status, Exception exception, String message, Object... messageArgs) { return FailureResult.of(new Failure(status, formatMessage(message, messageArgs), exception)); } /** * Formats the message using SLF4J. * * @param message the message * @param messageArgs the arguments for the message * @return the formatted message */ private static String formatMessage(String message, Object[] messageArgs) { return MessageFormatter.arrayFormat(message, messageArgs).getMessage(); } /** * Returns a failed result from another failed result. * This method ensures the result type matches the expected type. * * @param result a failure result * @param <U> the expected result type * @return a failure result of the expected type * @throws IllegalArgumentException if the result is a success */ @SuppressWarnings("unchecked") public static <U> Result<U> failure(Result<?> result) { if (result.isSuccess()) { throw new IllegalArgumentException("Result must be a failure"); } return (Result<U>) result; } /** * Creates a failed result cause by multiple failed results. * The input results can be successes or failures, only the failures will be included in the created result. * Intended to be used with {@link #anyFailures(Result[])}. * <code> * if (Result.anyFailures(result1, result2, result3) { * return Result.failures(result1, result2, result3); * } * </code> * * @param result1 the first result * @param result2 the second result * @param results the rest of the results * @param <U> the expected type of the result * @return a failed result wrapping multiple other failed results * @throws IllegalArgumentException if all of the results are successes */ public static <U> Result<U> failure(Result<?> result1, Result<?> result2, Result<?>... results) { ArgumentChecker.notNull(result1, "result1"); ArgumentChecker.notNull(result2, "result2"); List<Failure> failures = new ArrayList<>(); if (!result1.isSuccess()) { failures.addAll(result1.getFailures()); } if (!result2.isSuccess()) { failures.addAll(result2.getFailures()); } for (Result<?> result : results) { if (!result.isSuccess()) { failures.addAll(result.getFailures()); } } if (failures.isEmpty()) { throw new IllegalArgumentException("All results were successes"); } else { return FailureResult.of(failures); } } /** * Creates a failed result cause by multiple failed results. * The input results can be successes or failures, only the failures will be included in the created result. * Intended to be used with {@link #anyFailures(Iterable)}. * <code> * if (Result.anyFailures(results) { * return Result.failure(results); * } * </code> * * @param results multiple results, of which at least one must be a failure, not empty * @param <U> the expected type of the result * @return a failed result wrapping multiple other failed results * @throws IllegalArgumentException if results is empty or contains nothing but successes */ public static <U> Result<U> failure(Iterable<? extends Result<?>> results) { ArgumentChecker.notEmpty(results, "results"); List<Failure> failures = new ArrayList<>(); for (Result<?> result : results) { if (!result.isSuccess()) { failures.addAll(result.getFailures()); } } if (failures.isEmpty()) { throw new IllegalArgumentException("All results were successes"); } else { return FailureResult.of(failures); } } //------------------------------------------------------------------------- /** * Checks if all the results are successful. * * @param results the results to check * @return true if all of the results are successes */ public static boolean allSuccessful(Result<?>... results) { for (Result<?> result : results) { if (!result.isSuccess()) { return false; } } return true; } /** * Checks if all the results are successful. * * @param results the results to check * @return true if all of the results are successes */ public static boolean allSuccessful(Iterable<? extends Result<?>> results) { for (Result<?> result : results) { if (!result.isSuccess()) { return false; } } return true; } /** * Checks if any of the results are failures. * * @param results the results to check * @return true if any of the results are failures */ public static boolean anyFailures(Result<?>... results) { return !allSuccessful(results); } /** * Checks if any of the results are failures. * * @param results the results to check * @return true if any of the results are failures */ public static boolean anyFailures(Iterable<? extends Result<?>> results) { return !allSuccessful(results); } }