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

io.activej.common.collection.Try Maven / Gradle / Ivy

There is a newer version: 4.3-r8
Show newest version
/*
 * Copyright (C) 2020 ActiveJ LLC.
 *
 * 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 io.activej.common.collection;

import io.activej.common.exception.UncheckedException;
import io.activej.common.recycle.Recyclers;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.*;
import java.util.stream.Collector;

import static io.activej.common.Checks.checkArgument;
import static io.activej.common.Checks.checkState;

public final class Try {
	static {
		Recyclers.register(Try.class, aTry -> Recyclers.recycle(aTry.result));
	}

	private final T result;

	@Nullable
	private final Throwable throwable;

	private Try(@Nullable T result, @Nullable Throwable e) {
		this.result = result;
		this.throwable = e;
	}

	public static  Try of(@Nullable T result) {
		return new Try<>(result, null);
	}

	public static  Try of(@Nullable T result, @Nullable Throwable e) {
		checkArgument(result == null || e == null, "Either result or exception should be null");
		return new Try<>(result, e);
	}

	public static  Try ofException(@NotNull Throwable e) {
		return new Try<>(null, e);
	}

	public static  Try wrap(@NotNull Supplier computation) {
		try {
			return new Try<>(computation.get(), null);
		} catch (UncheckedException u) {
			return new Try<>(null, u.getCause());
		}
	}

	public static  Try wrap(@NotNull Runnable computation) {
		try {
			computation.run();
			return new Try<>(null, null);
		} catch (UncheckedException u) {
			return new Try<>(null, u.getCause());
		}
	}

	public static  Try wrap(@NotNull Callable computation) {
		try {
			@Nullable T result = computation.call();
			return new Try<>(result, null);
		} catch (UncheckedException u) {
			return new Try<>(null, u.getCause());
		} catch (RuntimeException e) {
			throw e;
		} catch (Exception e) {
			return new Try<>(null, e);
		}
	}

	public static Collector, ?, Try> voidReducer() {
		return reducer(($1, $2) -> null);
	}

	public static  Collector, ?, Try> reducer(@NotNull BinaryOperator combiner) {
		return reducer(null, combiner);
	}

	public static  Collector, ?, Try> reducer(@Nullable T identity, @NotNull BinaryOperator combiner) {
		class Accumulator {
			T result = identity;
			final List throwables = new ArrayList<>();
		}
		return Collector.of(Accumulator::new,
				(acc, t) -> {
					if (t.isSuccess()) {
						acc.result = acc.result != null ? combiner.apply(acc.result, t.get()) : t.get();
					} else {
						acc.throwables.add(t.getException());
					}
				},
				(acc1, acc2) -> {
					acc1.result = combiner.apply(acc1.result, acc2.result);
					acc1.throwables.addAll(acc2.throwables);
					return acc1;
				},
				acc -> {
					if (acc.throwables.isEmpty()) {
						return Try.of(acc.result);
					}
					Throwable e = acc.throwables.get(0);
					for (Throwable t : acc.throwables) {
						if (t != e) {
							e.addSuppressed(t);
						}
					}
					return Try.ofException(e);
				});
	}

	@Contract(pure = true)
	public boolean isSuccess() {
		return throwable == null;
	}

	@Contract(pure = true)
	public boolean isException() {
		return throwable != null;
	}

	@Contract(pure = true)
	public T get() {
		checkState(isSuccess());
		return result;
	}

	@Contract(pure = true)
	public T getOrThrow() throws Exception {
		if (throwable == null) {
			return result;
		}
		throw throwable instanceof Exception ? (Exception) throwable : new RuntimeException(throwable);
	}

	@Contract(pure = true)
	public T getOr(@Nullable T defaultValue) {
		return throwable == null ? result : defaultValue;
	}

	@Contract(pure = true)
	public T getOrSupply(@NotNull Supplier defaultValueSupplier) {
		return throwable == null ? result : defaultValueSupplier.get();
	}

	@Contract(pure = true)
	@Nullable
	public T getOrNull() {
		return result;
	}

	@Contract(pure = true)
	@NotNull
	public Throwable getException() {
		checkState(isException());
		return throwable;
	}

	@Contract(pure = true)
	@Nullable
	public Throwable getExceptionOrNull() {
		return throwable;
	}

	@NotNull
	public Try ifSuccess(@NotNull Consumer resultConsumer) {
		if (isSuccess()) {
			resultConsumer.accept(result);
		}
		return this;
	}

	@NotNull
	public Try ifException(@NotNull Consumer exceptionConsumer) {
		if (isException()) {
			exceptionConsumer.accept(throwable);
		}
		return this;
	}

	@NotNull
	public Try consume(@NotNull BiConsumer consumer) {
		consumer.accept(result, throwable);
		return this;
	}

	@NotNull
	public Try consume(@NotNull Consumer resultConsumer, @NotNull Consumer exceptionConsumer) {
		if (isSuccess()) {
			resultConsumer.accept(result);
		} else {
			exceptionConsumer.accept(throwable);
		}
		return this;
	}

	@SuppressWarnings("unchecked")
	@Contract(pure = true)
	@NotNull
	private  Try mold() {
		checkState(isException());
		return (Try) this;
	}

	@Contract(pure = true)
	public  U reduce(@NotNull Function function, @NotNull Function exceptionFunction) {
		return throwable == null ? function.apply(result) : exceptionFunction.apply(throwable);
	}

	@Contract(pure = true)
	public  U reduce(@NotNull BiFunction fn) {
		return fn.apply(result, throwable);
	}

	@Contract(pure = true)
	@NotNull
	public  Try map(@NotNull Function function) {
		if (throwable == null) {
			try {
				return new Try<>(function.apply(result), null);
			} catch (UncheckedException u) {
				return new Try<>(null, u.getCause());
			}
		}
		return mold();
	}

	@Contract(pure = true)
	@NotNull
	public  Try flatMap(@NotNull Function> function) {
		return throwable == null ? function.apply(result) : mold();
	}

	@Contract(pure = true)
	@NotNull
	public Either toEither() {
		return throwable == null ? Either.left(result) : Either.right(throwable);
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Try other = (Try) o;
		if (!Objects.equals(result, other.result)) return false;
		return Objects.equals(throwable, other.throwable);
	}

	@Override
	public int hashCode() {
		int hash = result != null ? result.hashCode() : 0;
		hash = 31 * hash + (throwable != null ? throwable.hashCode() : 0);
		return hash;
	}

	@Override
	public String toString() {
		return "{" + (isSuccess() ? "" + result : "" + throwable) + "}";
	}
}