/* * Copyright 2015 Jacek Marchwicki <jacek.marchwicki@gmail.com> * * 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.appunite.rx; import com.appunite.rx.functions.Functions1; import com.appunite.rx.functions.FunctionsN; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; import rx.Observable; import rx.functions.Func1; import rx.functions.Func2; import rx.functions.Func3; import static com.appunite.rx.internal.Preconditions.checkArgument; import static com.appunite.rx.internal.Preconditions.checkNotNull; import static com.appunite.rx.internal.Preconditions.checkState; /** * Class that represents data or error * @param <T> type of data */ public class ResponseOrError<T> { @Nullable private final T data; @Nullable private final Throwable error; private ResponseOrError(@Nullable T data, @Nullable Throwable error) { checkArgument(data != null ^ error != null); this.data = data; this.error = error; } /** * Returns error response from throwable * @param t throwable * @param <T> type of possible data * @return representation of throwable */ public static <T> ResponseOrError<T> fromError(@Nonnull Throwable t) { return new ResponseOrError<>(null, checkNotNull(t)); } /** * Returns data response from data * @param data data * @param <T> type of data * @return representation of data */ public static <T> ResponseOrError<T> fromData(@Nonnull T data) { return new ResponseOrError<>(checkNotNull(data), null); } /** * Returns data * * You need to check {@link #isData()} * * @return data * @throws java.lang.IllegalStateException when response is error */ @Nonnull public T data() { checkState(data != null); assert data != null; return data; } /** * Returns if response contains data * @return true if contains data */ public boolean isData() { return data != null; } /** * Returns if response contains error * * Helper for negation of {@link #isData()} * @return true if contains error */ public boolean isError() { return !isData(); } /** * Returns error * * You need to check {@link #isError()} * @return error throwable * @throws java.lang.IllegalStateException when response is data */ @Nonnull public Throwable error() { checkState(error != null); assert error != null; return error; } /** * Function that converts ResponseOrError to Boolean containing {@link #isData()} * @return function */ @Nonnull public static Func1<? super ResponseOrError<?>, Boolean> funcIsData() { return new Func1<ResponseOrError<?>, Boolean>() { @Override public Boolean call(ResponseOrError<?> response) { return response.isData(); } }; } /** * Function that converts ResponseOrError to Boolean containing {@link #isError()} ()} * @return function */ @Nonnull public static Func1<? super ResponseOrError<?>, Boolean> funcIsError() { return Functions1.neg(funcIsData()); } /** * Function that converts ResponseOrError to null if {@link #isData()} or {@link #error()} Throwable * @return function */ @Nonnull public static Func1<? super ResponseOrError<?>, Throwable> toNullableThrowable() { return new Func1<ResponseOrError<?>, Throwable>() { @Override @Nullable public Throwable call(final ResponseOrError<?> responseOrError) { //noinspection ThrowableResultOfMethodCallIgnored return responseOrError.isData() ? null : responseOrError.error(); } }; } public <W> ResponseOrError<W> replaceIfDataWith(final W element) { return isData() ? ResponseOrError.fromData(element) : ResponseOrError.<W>fromError(error()); } /** * Parameters are equals using {@link Object#equals(Object)} on data or error * @param o object * @return true if equals */ @Override public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof ResponseOrError)) return false; final ResponseOrError that = (ResponseOrError) o; return !(data != null ? !data.equals(that.data) : that.data != null) && !(error != null ? !error.equals(that.error) : that.error != null); } /** * Hash code using {@link Object#hashCode()} on data or error * @return hash code */ @Override public int hashCode() { int result = data != null ? data.hashCode() : 0; result = 31 * result + (error != null ? error.hashCode() : 0); return result; } @Override public String toString() { return "ResponseOrError{" + "data=" + data + ", error=" + error + '}'; } /** * Convert {@link Observable} that can throw error to {@link Observable<ResponseOrError>} * * If source Observable returns obj, result observable will return ResponseOrError.fromData(obj) * If source Observable throws err, result observable will return ResponseOrError.fromError(err) * * @param <T> type of data * @return observable */ @Nonnull public static <T> Observable.Transformer<T, ResponseOrError<T>> toResponseOrErrorObservable() { return new Observable.Transformer<T, ResponseOrError<T>>() { @Override public Observable<ResponseOrError<T>> call(final Observable<T> observable) { return toResponseOrErrorObservable(observable); } }; } @Nonnull private static <T> Observable<ResponseOrError<T>> toResponseOrErrorObservable(@Nonnull Observable<T> observable) { return observable .map(new Func1<T, ResponseOrError<T>>() { @Override public ResponseOrError<T> call(final T t) { return ResponseOrError.fromData(t); } }) .onErrorResumeNext(new Func1<Throwable, Observable<? extends ResponseOrError<T>>>() { @Override public Observable<? extends ResponseOrError<T>> call(final Throwable throwable) { return Observable.just(ResponseOrError.<T>fromError(throwable)); } }); } /** * Map only success response ignoring error * * <pre> * {@code * Observable<ResponseOrError<Boolean>> output = * Observable.just(ResponseOrError.fromData("text") * .compose(ResponseOrError.map(new Func<String, Boolean) { * Boolean call(String in) { * return in != null; * } * }); * } * </pre> * * @param func that maps data of ResponseOrError to another data * @param <T> type of source object * @param <K> type of destination object * @return observable */ @Nonnull public static <T, K> Observable.Transformer<ResponseOrError<T>, ResponseOrError<K>> map(@Nonnull final Func1<T, K> func) { return new Observable.Transformer<ResponseOrError<T>, ResponseOrError<K>>() { @Override public Observable<ResponseOrError<K>> call(final Observable<ResponseOrError<T>> observable) { return map(observable, func); } }; } @Nonnull private static <T, K> Observable<ResponseOrError<K>> map(@Nonnull final Observable<ResponseOrError<T>> observable, @Nonnull final Func1<T, K> func) { checkNotNull(observable); checkNotNull(func); return observable.map(new Func1<ResponseOrError<T>, ResponseOrError<K>>() { @Override public ResponseOrError<K> call(final ResponseOrError<T> response) { if (response.isError()) { return ResponseOrError.fromError(response.error()); } else { return ResponseOrError.fromData(func.call(response.data())); } } }); } /** * Flat map only success response ignoring error * * <pre> * {@code * Observable<ResponseOrError<Boolean>> output = * Observable.just(ResponseOrError.fromData("text") * .compose(ResponseOrError.map(new Func<String, Observable<Boolean>) { * Observable<Boolean> call(String in) { * return Observable.just(in != null); * } * }); * } * </pre> * * @param func that maps data of ResponseOrError to Observable * @param <T> type of source object * @param <K> type of destination object * @return observable */ @Nonnull public static <T, K> Observable.Transformer<ResponseOrError<T>, ResponseOrError<K>> flatMap(@Nonnull final Func1<T, Observable<ResponseOrError<K>>> func) { return new Observable.Transformer<ResponseOrError<T>, ResponseOrError<K>>() { @Override public Observable<ResponseOrError<K>> call(final Observable<ResponseOrError<T>> observableObservable) { return flatMap(observableObservable, func); } }; } @Nonnull private static <T, K> Observable<ResponseOrError<K>> flatMap(@Nonnull final Observable<ResponseOrError<T>> observable, @Nonnull final Func1<T, Observable<ResponseOrError<K>>> func) { checkNotNull(observable); checkNotNull(func); return observable.flatMap(new Func1<ResponseOrError<T>, Observable<ResponseOrError<K>>>() { @Override public Observable<ResponseOrError<K>> call(final ResponseOrError<T> response) { if (response.isError()) { return Observable.just(ResponseOrError.<K>fromError(response.error())); } else { return func.call(response.data()); } } }); } /** * Switch map only success response ignoring error * * <pre> * {@code * Observable<ResponseOrError<Boolean>> output = * Observable.just(ResponseOrError.fromData("text") * .compose(ResponseOrError.switchMap(new Func<String, Observable<Boolean>) { * Observable<Boolean> call(String in) { * return Observable.just(in != null); * } * }); * } * </pre> * * @param func that maps data of ResponseOrError to Observable * @param <T> type of source object * @param <K> type of destination object * @return observable */ @Nonnull public static <T, K> Observable.Transformer<ResponseOrError<T>, ResponseOrError<K>> switchMap(@Nonnull final Func1<T, Observable<ResponseOrError<K>>> func) { return new Observable.Transformer<ResponseOrError<T>, ResponseOrError<K>>() { @Override public Observable<ResponseOrError<K>> call(final Observable<ResponseOrError<T>> observableObservable) { return switchMap(observableObservable, func); } }; } @Nonnull private static <T, K> Observable<ResponseOrError<K>> switchMap(@Nonnull final Observable<ResponseOrError<T>> observable, @Nonnull final Func1<T, Observable<ResponseOrError<K>>> func) { checkNotNull(observable); checkNotNull(func); return observable.switchMap(new Func1<ResponseOrError<T>, Observable<ResponseOrError<K>>>() { @Override public Observable<ResponseOrError<K>> call(final ResponseOrError<T> response) { if (response.isError()) { return Observable.just(ResponseOrError.<K>fromError(response.error())); } else { return func.call(response.data()); } } }); } /** * Returns only success response of observable of {@link ResponseOrError} and convert to {@link #data()} * @param <T> type ResponseOrError * @return observable */ @Nonnull public static <T> Observable.Transformer<ResponseOrError<T>, T> onlySuccess() { return new Observable.Transformer<ResponseOrError<T>, T>() { @Override public Observable<T> call(final Observable<ResponseOrError<T>> observable) { return onlySuccess(observable); } }; } @Nonnull private static <T> Observable<T> onlySuccess(@Nonnull final Observable<ResponseOrError<T>> observable) { return observable.filter(funcIsData()).map(new Func1<ResponseOrError<T>, T>() { @Override public T call(final ResponseOrError<T> response) { return response.data(); } }); } /** * Returns only error response of observable of {@link ResponseOrError} and convert to {@link #error()} * @param <T> type of ResponseOrError * @return observable */ @Nonnull public static <T> Observable.Transformer<ResponseOrError<T>, Throwable> onlyError() { return new Observable.Transformer<ResponseOrError<T>, Throwable>() { @Override public Observable<Throwable> call(final Observable<ResponseOrError<T>> observable) { return onlyError(observable); } }; } @Nonnull private static <T> Observable<Throwable> onlyError(@Nonnull final Observable<ResponseOrError<T>> observable) { return observable.filter(funcIsError()).map(new Func1<ResponseOrError<?>, Throwable>() { @Override public Throwable call(final ResponseOrError<?> responseOrError) { return responseOrError.error(); } }); } /** * Converts lists of ResponseOrError to ResponseOrError with list * * If there will be error only first will be returned * @param <T> type of element * @return observable */ @Nonnull public static <T> Observable.Transformer<List<ResponseOrError<T>>, ResponseOrError<List<T>>> newFromListObservable() { return new Observable.Transformer<List<ResponseOrError<T>>, ResponseOrError<List<T>>>() { @Override public Observable<ResponseOrError<List<T>>> call(final Observable<List<ResponseOrError<T>>> observable) { return fromListObservable(observable); } }; } @Nonnull private static <T> Observable<ResponseOrError<List<T>>> fromListObservable( @Nonnull final Observable<List<ResponseOrError<T>>> observable) { return observable.map(new Func1<List<ResponseOrError<T>>, ResponseOrError<List<T>>>() { @Override public ResponseOrError<List<T>> call(final List<ResponseOrError<T>> responses) { for (ResponseOrError<T> response : responses) { if (response.isError()) { return ResponseOrError.fromError(response.error()); } } final ArrayList<T> ret = new ArrayList<>(responses.size()); for (ResponseOrError<T> response : responses) { ret.add(response.data()); } return ResponseOrError.fromData(Collections.unmodifiableList(ret)); } }); } /** * Returns true unit all observables will returns some value * * @param observables observables that returns some data * @return observable */ @Nonnull public static Observable<Boolean> combineProgressObservable(@Nonnull List<Observable<ResponseOrError<?>>> observables) { return Observable.combineLatest(observables, FunctionsN.returnFalse()) .startWith(true); } /** * Converts {@code ResponseOrError<T>} to {@code ResponseOrError<?>} * * @param observable to transform * @param <T> type of source observable * @return observable * @see #combineErrorsObservable(List) * @see #combineProgressObservable(List) */ @Nonnull public static <T> Observable<ResponseOrError<?>> transform(@Nonnull Observable<ResponseOrError<T>> observable) { return observable .map(new Func1<ResponseOrError<T>, ResponseOrError<?>>() { @Override public ResponseOrError<?> call(ResponseOrError<T> tResponseOrError) { return tResponseOrError; } }); } /** * Returns throwable if some observable returned {@link #error()} otherwise null * * @param observables source observables * @return observable */ @Nonnull public static Observable<Throwable> combineErrorsObservable(@Nonnull List<Observable<ResponseOrError<?>>> observables) { final ArrayList<Observable<Throwable>> ret = new ArrayList<>(); for (int i = 0; i < observables.size(); i++) { ret.add(observables.get(i).map(ResponseOrError.toNullableThrowable()).startWith((Throwable) null)); } return Observable.combineLatest(Collections.unmodifiableList(ret), FunctionsN.combineFirstThrowable()); } @Nonnull public static <T1, T2, R> Func2<ResponseOrError<T1>, T2, ResponseOrError<R>> toErrorFunc2(@Nonnull final Func2<T1, T2, R> func) { return new Func2<ResponseOrError<T1>, T2, ResponseOrError<R>>() { @Override public ResponseOrError<R> call(ResponseOrError<T1> t1ResponseOrError, T2 t2) { if (t1ResponseOrError.isData()) { return ResponseOrError.fromData(func.call(t1ResponseOrError.data(), t2)); } else { return ResponseOrError.fromError(t1ResponseOrError.error()); } } }; } @Nonnull public static <T1, T2, T3, R> Func3<ResponseOrError<T1>, ResponseOrError<T2>, T3, ResponseOrError<R>> toErrorFunc3(@Nonnull final Func3<T1, T2, T3, R> func) { return new Func3<ResponseOrError<T1>, ResponseOrError<T2>, T3, ResponseOrError<R>>() { @Override public ResponseOrError<R> call(ResponseOrError<T1> t1ResponseOrError, ResponseOrError<T2> t2ResponseOrError, T3 t3) { if (t1ResponseOrError.isData() && t2ResponseOrError.isData()) { return ResponseOrError.fromData(func.call(t1ResponseOrError.data(), t2ResponseOrError.data, t3)); } else if (t1ResponseOrError.isError()){ return ResponseOrError.fromError(t1ResponseOrError.error()); } else { return ResponseOrError.fromError(t2ResponseOrError.error()); } } }; } }