From 19f77a1bdd62eed2dc37b5a0e7ed704159b59fc2 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Sun, 16 Apr 2017 22:56:55 +0300 Subject: [PATCH 1/5] general improvements added according to latest library usage statistics --- build.gradle | 3 +- .../core/observables/BaseChangeable.java | 112 ++++ .../roboswag/core/observables/Changeable.java | 71 +-- .../core/observables/NonNullChangeable.java | 35 +- .../core/observables/ObservableResult.java | 59 --- .../OnSubscribeRefCountWithCacheTime.java | 101 ++-- .../core/observables/RxAndroidUtils.java | 71 ++- .../collections/ObservableCollection.java | 60 +-- .../collections/ObservableFilteredList.java | 113 ++-- .../collections/ObservableList.java | 13 +- .../loadable/LoadedRenewableItems.java | 49 -- .../collections/loadable/LoadingMoreList.java | 48 +- .../loadable/LoadingRenewableList.java | 241 --------- .../loadable/NewerItemsLoader.java | 40 -- .../loadable/NewerLoadRequest.java | 74 --- .../observables/storable/BaseStorable.java | 482 ++++++++++++++++++ .../core/observables/storable/Migration.java | 2 +- .../core/observables/storable/Storable.java | 428 +--------------- .../core/observables/storable/Store.java | 3 +- .../builders/NonNullStorableBuilder.java | 113 ---- .../storable/concrete/NonNullStorable.java | 113 +++- .../touchin/roboswag/core/utils/Optional.java | 71 +++ .../roboswag/core/utils/ServiceBinder.java | 20 + .../roboswag/core/utils/ThreadLocalValue.java | 25 +- .../core/utils/pairs/HalfNullablePair.java | 4 +- .../core/utils/pairs/NonNullPair.java | 4 +- .../core/utils/pairs/NullablePair.java | 4 +- 27 files changed, 1065 insertions(+), 1294 deletions(-) create mode 100644 src/main/java/ru/touchin/roboswag/core/observables/BaseChangeable.java delete mode 100644 src/main/java/ru/touchin/roboswag/core/observables/ObservableResult.java delete mode 100644 src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadedRenewableItems.java delete mode 100644 src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadingRenewableList.java delete mode 100644 src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/NewerItemsLoader.java delete mode 100644 src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/NewerLoadRequest.java create mode 100644 src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java delete mode 100644 src/main/java/ru/touchin/roboswag/core/observables/storable/builders/NonNullStorableBuilder.java create mode 100644 src/main/java/ru/touchin/roboswag/core/utils/Optional.java diff --git a/build.gradle b/build.gradle index 14e5223..e711b15 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ android { } defaultConfig { - minSdkVersion 9 + minSdkVersion 16 targetSdkVersion 25 } } @@ -19,4 +19,5 @@ android { dependencies { provided 'com.android.support:support-annotations:25.3.1' provided 'io.reactivex:rxandroid:1.2.1' + provided 'io.reactivex:rxjava:1.2.9' } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/BaseChangeable.java b/src/main/java/ru/touchin/roboswag/core/observables/BaseChangeable.java new file mode 100644 index 0000000..a4efc1c --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/core/observables/BaseChangeable.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * 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 ru.touchin.roboswag.core.observables; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import ru.touchin.roboswag.core.utils.ObjectUtils; +import ru.touchin.roboswag.core.utils.Optional; +import rx.Observable; +import rx.subjects.BehaviorSubject; + +/** + * Created by Gavriil Sitnikov on 24/03/2016. + * Wrapper over {@link BehaviorSubject} which could be serialized. + * Such object is useful as view model and also as value in Android that could be passed into {@link android.os.Bundle}. + * + * @param Type of Changeable value; + * @param Type of actual value operating by Changeable. Could be same as {@link TValue}. + */ +public abstract class BaseChangeable implements Serializable { + + private static final long serialVersionUID = 1L; + + private transient BehaviorSubject> valueSubject; + + public BaseChangeable(@Nullable final TValue defaultValue) { + valueSubject = BehaviorSubject.create(new Optional<>(defaultValue)); + } + + @NonNull + protected Observable> observeOptionalValue() { + return valueSubject.distinctUntilChanged(); + } + + /** + * Sets current value. + * + * @param value Value to set. + */ + public void set(@Nullable final TValue value) { + valueSubject.onNext(new Optional<>(value)); + } + + /** + * Returns current value. + * + * @return Current value. + */ + @Nullable + public TValue get() { + return valueSubject.getValue().getValue(); + } + + /** + * Returns {@link Observable} which is emits current value and then emitting changes of current value. + * + * @return Current value {@link Observable}. + */ + @NonNull + public abstract Observable observe(); + + private void writeObject(@NonNull final ObjectOutputStream outputStream) throws IOException { + outputStream.writeObject(valueSubject.getValue()); + } + + @SuppressWarnings("unchecked") + private void readObject(@NonNull final ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + valueSubject = BehaviorSubject.create((Optional) inputStream.readObject()); + } + + @Override + public boolean equals(@Nullable final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + + final BaseChangeable that = (BaseChangeable) object; + return ObjectUtils.equals(valueSubject.getValue(), that.valueSubject.getValue()); + } + + @Override + public int hashCode() { + return valueSubject.getValue() != null ? valueSubject.getValue().hashCode() : 0; + } + +} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/Changeable.java b/src/main/java/ru/touchin/roboswag/core/observables/Changeable.java index 171fc42..e878f38 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/Changeable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/Changeable.java @@ -22,47 +22,19 @@ package ru.touchin.roboswag.core.observables; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; - -import ru.touchin.roboswag.core.utils.ObjectUtils; +import ru.touchin.roboswag.core.utils.Optional; import rx.Observable; -import rx.subjects.BehaviorSubject; /** * Created by Gavriil Sitnikov on 24/03/2016. - * Wrapper over {@link BehaviorSubject} which could be serialized. - * Such object is useful as view model and also as value in Android that could be passed into {@link android.os.Bundle}. + * Variant of {@link BaseChangeable} which is allows to set nullable values. + * Needed to separate non-null Changeable from nullable Changeable. */ -public class Changeable implements Serializable { - - private static final long serialVersionUID = 1L; - - private transient BehaviorSubject subject; +//COMPATIBILITY NOTE: in RxJava2 it should extends BaseChangeable> +public class Changeable extends BaseChangeable { public Changeable(@Nullable final T defaultValue) { - subject = BehaviorSubject.create(defaultValue); - } - - /** - * Sets current value. - * - * @param value Value to set. - */ - public void set(@Nullable final T value) { - subject.onNext(value); - } - - /** - * Returns current value. - * - * @return Current value. - */ - @Nullable - public T get() { - return subject.getValue(); + super(defaultValue); } /** @@ -71,35 +43,10 @@ public class Changeable implements Serializable { * @return Current value {@link Observable}. */ @NonNull + @Override + //COMPATIBILITY NOTE: in RxJava2 it should be Observable> public Observable observe() { - return subject.distinctUntilChanged(); - } - - private void writeObject(@NonNull final ObjectOutputStream outputStream) throws IOException { - outputStream.writeObject(subject.getValue()); - } - - @SuppressWarnings("unchecked") - private void readObject(@NonNull final ObjectInputStream inputStream) throws IOException, ClassNotFoundException { - subject = BehaviorSubject.create((T) inputStream.readObject()); - } - - @Override - public boolean equals(@Nullable final Object object) { - if (this == object) { - return true; - } - if (object == null || getClass() != object.getClass()) { - return false; - } - - final Changeable that = (Changeable) object; - return ObjectUtils.equals(subject.getValue(), that.subject.getValue()); - } - - @Override - public int hashCode() { - return subject.getValue() != null ? subject.getValue().hashCode() : 0; + return observeOptionalValue().map(Optional::getValue); } } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/NonNullChangeable.java b/src/main/java/ru/touchin/roboswag/core/observables/NonNullChangeable.java index 49d15e0..85b54de 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/NonNullChangeable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/NonNullChangeable.java @@ -21,18 +21,22 @@ package ru.touchin.roboswag.core.observables; import android.support.annotation.NonNull; +import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; +import rx.Observable; /** * Created by Gavriil Sitnikov on 24/03/2016. - * Variant of {@link Changeable} which is allows to set only non-null values. + * Variant of {@link BaseChangeable} which is allows to set only non-null values. + * Needed to separate non-null Changeable from nullable Changeable. */ -public class NonNullChangeable extends Changeable { - - private static final long serialVersionUID = 1L; +public class NonNullChangeable extends BaseChangeable { public NonNullChangeable(@NonNull final T defaultValue) { super(defaultValue); + if (defaultValue == null) { + throw new ShouldNotHappenException(); + } } @NonNull @@ -45,11 +49,30 @@ public class NonNullChangeable extends Changeable { return value; } - @SuppressWarnings("PMD.UselessOverridingMethod") - // UselessOverridingMethod: we need only annotation change @Override public void set(@NonNull final T value) { + if (value == null) { + Lc.assertion("value is null"); + return; + } super.set(value); } + /** + * Returns {@link Observable} which is emits current value and then emitting changes of current value. + * + * @return Current value {@link Observable}. + */ + @NonNull + @Override + public Observable observe() { + return observeOptionalValue() + .map(optional -> { + if (optional.getValue() == null) { + throw new ShouldNotHappenException(); + } + return optional.getValue(); + }); + } + } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/ObservableResult.java b/src/main/java/ru/touchin/roboswag/core/observables/ObservableResult.java deleted file mode 100644 index 686fe90..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/ObservableResult.java +++ /dev/null @@ -1,59 +0,0 @@ -package ru.touchin.roboswag.core.observables; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * Created by Gavriil Sitnikov on 21/05/2016. - * Object represents Observable's execution result. Contains all items and errors emitted by Observable during subscription. - */ -public class ObservableResult { - - @NonNull - private final List items = new LinkedList<>(); - @Nullable - private Throwable error; - - /** - * Passes item to collect. - * - * @param item Emitted item. - */ - public void onNext(@Nullable final T item) { - items.add(item); - } - - /** - * Passes error to collect. - * - * @param error Emitted error. - */ - public void onError(@NonNull final Throwable error) { - this.error = error; - } - - /** - * Returns list of collected items. - * - * @return Items. - */ - @NonNull - public List getItems() { - return new ArrayList<>(items); - } - - /** - * Returns collected error. - * - * @return Error. - */ - @Nullable - public Throwable getError() { - return error; - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/OnSubscribeRefCountWithCacheTime.java b/src/main/java/ru/touchin/roboswag/core/observables/OnSubscribeRefCountWithCacheTime.java index d428aab..e6b7de2 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/OnSubscribeRefCountWithCacheTime.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/OnSubscribeRefCountWithCacheTime.java @@ -28,7 +28,6 @@ import rx.Observable.OnSubscribe; import rx.Scheduler; import rx.Subscriber; import rx.Subscription; -import rx.functions.Action0; import rx.functions.Action1; import rx.observables.ConnectableObservable; import rx.schedulers.Schedulers; @@ -114,19 +113,15 @@ public final class OnSubscribeRefCountWithCacheTime implements OnSubscribe @NonNull private Action1 onSubscribe(@NonNull final Subscriber subscriber, @NonNull final AtomicBoolean writeLocked) { - return new Action1() { - @Override - public void call(@NonNull final Subscription subscription) { - - try { - baseSubscription.add(subscription); - // ready to subscribe to source so do it - doSubscribe(subscriber, baseSubscription); - } finally { - // release the write lock - lock.unlock(); - writeLocked.set(false); - } + return subscription -> { + try { + baseSubscription.add(subscription); + // ready to subscribe to source so do it + doSubscribe(subscriber, baseSubscription); + } finally { + // release the write lock + lock.unlock(); + writeLocked.set(false); } }; } @@ -152,14 +147,14 @@ public final class OnSubscribeRefCountWithCacheTime implements OnSubscribe } private void cleanup() { - // on error or completion we need to unsubscribe the base subscription - // and set the subscriptionCount to 0 + // on error or completion we need to unsubscribe the base subscription and set the subscriptionCount to 0 lock.lock(); try { if (baseSubscription == currentBase) { - if (worker != null) { - worker.unsubscribe(); - worker = null; + cleanupWorker(); + // backdoor into the ConnectableObservable to cleanup and reset its state + if (source instanceof Subscription) { + ((Subscription) source).unsubscribe(); } baseSubscription.unsubscribe(); baseSubscription = new CompositeSubscription(); @@ -174,41 +169,45 @@ public final class OnSubscribeRefCountWithCacheTime implements OnSubscribe @NonNull private Subscription disconnect(@NonNull final CompositeSubscription current) { - return Subscriptions.create(new Action0() { - - @Override - public void call() { - lock.lock(); - try { - if (baseSubscription == current && subscriptionCount.decrementAndGet() == 0) { - if (worker != null) { - worker.unsubscribe(); - } else { - worker = scheduler.createWorker(); - } - worker.schedule(new Action0() { - @Override - public void call() { - lock.lock(); - try { - if (subscriptionCount.get() == 0) { - baseSubscription.unsubscribe(); - // need a new baseSubscription because once - // unsubscribed stays that way - worker.unsubscribe(); - worker = null; - baseSubscription = new CompositeSubscription(); - } - } finally { - lock.unlock(); - } - } - }, cacheTime, cacheTimeUnit); + return Subscriptions.create(() -> { + lock.lock(); + try { + if (baseSubscription == current && subscriptionCount.decrementAndGet() == 0) { + if (worker != null) { + worker.unsubscribe(); + } else { + worker = scheduler.createWorker(); } - } finally { - lock.unlock(); + worker.schedule(() -> { + lock.lock(); + try { + if (subscriptionCount.get() == 0) { + cleanupWorker(); + // backdoor into the ConnectableObservable to cleanup and reset its state + if (source instanceof Subscription) { + ((Subscription) source).unsubscribe(); + } + baseSubscription.unsubscribe(); + // need a new baseSubscription because once + // unsubscribed stays that way + baseSubscription = new CompositeSubscription(); + } + } finally { + lock.unlock(); + } + }, cacheTime, cacheTimeUnit); } + } finally { + lock.unlock(); } }); } + + private void cleanupWorker() { + if (worker != null) { + worker.unsubscribe(); + worker = null; + } + } + } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/RxAndroidUtils.java b/src/main/java/ru/touchin/roboswag/core/observables/RxAndroidUtils.java index 96ff8d6..ef35ab1 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/RxAndroidUtils.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/RxAndroidUtils.java @@ -20,9 +20,11 @@ package ru.touchin.roboswag.core.observables; import android.app.Service; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; import android.os.IBinder; import android.os.Looper; @@ -33,9 +35,9 @@ import java.util.concurrent.CountDownLatch; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.ServiceBinder; +import rx.Emitter; import rx.Observable; import rx.Scheduler; -import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; /** @@ -55,23 +57,50 @@ public final class RxAndroidUtils { @NonNull public static Observable observeService(@NonNull final Context context, @NonNull final Class serviceClass) { return Observable - .just(new SubscribeServiceConnection()) - .switchMap(serviceConnection -> Observable - .create(subscriber -> { - serviceConnection.subscriber = subscriber; - context.bindService(new Intent(context, serviceClass), serviceConnection, Context.BIND_AUTO_CREATE); - }) - .doOnUnsubscribe(() -> context.unbindService(serviceConnection))) + .just(new OnSubscribeServiceConnection()) + .switchMap(onSubscribeServiceConnection -> Observable + .create(emitter -> { + onSubscribeServiceConnection.emitter = emitter; + context.bindService(new Intent(context, serviceClass), onSubscribeServiceConnection, Context.BIND_AUTO_CREATE); + }, Emitter.BackpressureMode.LATEST) + .doOnUnsubscribe(() -> { + context.unbindService(onSubscribeServiceConnection); + onSubscribeServiceConnection.emitter = null; + })) .distinctUntilChanged() .replay(1) .refCount(); } + /** + * Observes classic Android broadcast with {@link BroadcastReceiver} as source of Observable items and Intent as items. + * + * @param context Context to register {@link BroadcastReceiver}; + * @param intentFilter {@link IntentFilter} to register {@link BroadcastReceiver}; + * @return Observable that observes Android broadcasts. + */ + @NonNull + public static Observable observeBroadcastEvent(@NonNull final Context context, @NonNull final IntentFilter intentFilter) { + return Observable + .just(new OnSubscribeBroadcastReceiver()) + .switchMap(onOnSubscribeBroadcastReceiver -> Observable + .create(emitter -> { + onOnSubscribeBroadcastReceiver.emitter = emitter; + context.registerReceiver(onOnSubscribeBroadcastReceiver, intentFilter); + }, Emitter.BackpressureMode.LATEST) + .doOnUnsubscribe(() -> { + context.unregisterReceiver(onOnSubscribeBroadcastReceiver); + onOnSubscribeBroadcastReceiver.emitter = null; + })) + .share(); + } + /** * Creating {@link Scheduler} that is scheduling work on specific thread with {@link Looper}. * Do not use it much times - it is creating endless thread every call. * It's good to use it only like a constant like: * private static final Scheduler SCHEDULER = RxAndroidUtils.createLooperScheduler(); + * IMPORTANT NOTE: looper thread will live forever! Do not create a lot of such Schedulers. * * @return Looper thread based {@link Scheduler}. */ @@ -91,20 +120,19 @@ public final class RxAndroidUtils { private RxAndroidUtils() { } - private static class SubscribeServiceConnection implements ServiceConnection { - + private static class OnSubscribeServiceConnection implements ServiceConnection { @Nullable - private Subscriber subscriber; + private Emitter emitter; @SuppressWarnings("unchecked") @Override public void onServiceConnected(@NonNull final ComponentName name, @Nullable final IBinder service) { - if (subscriber == null) { + if (emitter == null) { return; } if (service instanceof ServiceBinder) { - subscriber.onNext((T) ((ServiceBinder) service).getService()); + emitter.onNext((TService) ((ServiceBinder) service).getService()); } else { Lc.assertion("IBinder should be instance of ServiceBinder."); } @@ -112,8 +140,21 @@ public final class RxAndroidUtils { @Override public void onServiceDisconnected(@NonNull final ComponentName name) { - if (subscriber != null) { - subscriber.onNext(null); + // service have been killed/crashed and destroyed. instead of emit null just wait service reconnection. + // even if someone keeps reference to dead service it is problem of service object to work correctly after destroy. + } + + } + + private static class OnSubscribeBroadcastReceiver extends BroadcastReceiver { + + @Nullable + private Emitter emitter; + + @Override + public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { + if (emitter != null) { + emitter.onNext(intent); } } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableCollection.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableCollection.java index 4a981e4..d759085 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableCollection.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableCollection.java @@ -25,25 +25,21 @@ import android.support.annotation.Nullable; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; +import rx.Emitter; import rx.Observable; -import rx.Subscriber; /** * Created by Gavriil Sitnikov on 23/05/16. * Class to represent collection which is providing it's inner changes in Rx observable way. * Use {@link #observeChanges()} and {@link #observeItems()} to observe collection changes. - * Use {@link #loadItem(int)} to load item asynchronously. * Methods {@link #size()} and {@link #get(int)} will return only already loaded items info. * * @param Type of collection's items. */ -public abstract class ObservableCollection implements Serializable { +public abstract class ObservableCollection { private int changesCount; @NonNull @@ -51,7 +47,7 @@ public abstract class ObservableCollection implements Serializable { @NonNull private transient Observable> itemsObservable; @Nullable - private transient Subscriber> changesSubscriber; + private transient Emitter> changesEmitter; public ObservableCollection() { this.changesObservable = createChangesObservable(); @@ -61,19 +57,16 @@ public abstract class ObservableCollection implements Serializable { @NonNull private Observable> createChangesObservable() { return Observable - .>create(subscriber -> this.changesSubscriber = subscriber) - .doOnUnsubscribe(() -> this.changesSubscriber = null) - .replay(0) - .refCount(); + .>create(emitter -> this.changesEmitter = emitter, Emitter.BackpressureMode.BUFFER) + .doOnUnsubscribe(() -> this.changesEmitter = null) + .share(); } @NonNull private Observable> createItemsObservable() { return Observable - .>switchOnNext(Observable.create(subscriber -> { - subscriber.onNext(observeChanges().map(changes -> getItems()).startWith(getItems())); - subscriber.onCompleted(); - })) + //switchOnNext to calculate getItems() on subscription but not on that method calling moment + .switchOnNext(Observable.fromCallable(() -> observeChanges().map(changes -> getItems()).startWith(getItems()))) .replay(1) .refCount(); } @@ -102,9 +95,12 @@ public abstract class ObservableCollection implements Serializable { * @param changes Changes of collection. */ protected void notifyAboutChanges(@NonNull final Collection> changes) { + if (changes.isEmpty()) { + return; + } changesCount++; - if (changesSubscriber != null) { - changesSubscriber.onNext(new CollectionChange<>(changesCount, Collections.unmodifiableCollection(changes))); + if (changesEmitter != null) { + changesEmitter.onNext(new CollectionChange<>(changesCount, Collections.unmodifiableCollection(changes))); } } @@ -164,36 +160,6 @@ public abstract class ObservableCollection implements Serializable { return size() == 0; } - /** - * Returns {@link Observable} which is loading item by position. - * It could return null in onNext callback if there is no item to load for such position. - * - * @param position Position to load item; - * @return {@link Observable} to load item. - */ - @NonNull - public abstract Observable loadItem(int position); - - /** - * Returns {@link Observable} which is loading item by range. - * It will return collection of loaded items in onNext callback. - * - * @param first First position of item to load; - * @param last Last position of item to load; - * @return {@link Observable} to load items. - */ - @NonNull - public Observable> loadRange(final int first, final int last) { - final List> itemsRequests = new ArrayList<>(); - for (int i = first; i <= last; i++) { - itemsRequests.add(loadItem(i)); - } - return Observable.concatEager(itemsRequests) - .filter(loadedItem -> loadedItem != null) - .toList() - .map(Collections::unmodifiableCollection); - } - private void writeObject(@NonNull final ObjectOutputStream outputStream) throws IOException { outputStream.writeInt(changesCount); } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableFilteredList.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableFilteredList.java index d75e721..0d4ff8f 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableFilteredList.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableFilteredList.java @@ -8,9 +8,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import rx.Observable; +import rx.Subscription; import rx.functions.Func1; +import rx.schedulers.Schedulers; /** * Created by Gavriil Sitnikov on 02/06/2016. @@ -23,7 +23,10 @@ public class ObservableFilteredList extends ObservableCollection { @NonNull private static List filterCollection(@NonNull final Collection sourceCollection, - @NonNull final Func1 filter) { + @Nullable final Func1 filter) { + if (filter == null) { + return new ArrayList<>(sourceCollection); + } final List result = new ArrayList<>(sourceCollection.size()); for (final TItem item : sourceCollection) { if (filter.call(item)) { @@ -33,35 +36,39 @@ public class ObservableFilteredList extends ObservableCollection { return result; } - @Nullable + @NonNull private List filteredList; - @Nullable - private Collection sourceCollection; + @NonNull + private ObservableCollection sourceCollection; @Nullable private Func1 filter; - - public ObservableFilteredList() { - super(); - //do nothing - } - - public ObservableFilteredList(@NonNull final Collection sourceCollection) { - super(); - this.sourceCollection = new ArrayList<>(sourceCollection); - this.filteredList = new ArrayList<>(sourceCollection); - } + @Nullable + private Subscription sourceCollectionSubscription; public ObservableFilteredList(@NonNull final Func1 filter) { - super(); - this.filter = filter; + this(new ArrayList<>(), filter); } - public ObservableFilteredList(@NonNull final Collection sourceCollection, - @NonNull final Func1 filter) { + public ObservableFilteredList(@NonNull final Collection sourceCollection, @Nullable final Func1 filter) { + this(new ObservableList<>(sourceCollection), filter); + } + + public ObservableFilteredList(@NonNull final ObservableCollection sourceCollection, @Nullable final Func1 filter) { super(); - this.sourceCollection = new ArrayList<>(sourceCollection); this.filter = filter; - filteredList = filterCollection(this.sourceCollection, this.filter); + this.sourceCollection = sourceCollection; + this.filteredList = filterCollection(this.sourceCollection.getItems(), this.filter); + update(); + } + + /** + * Sets collection of items to filter. + * + * @param sourceCollection Collection with items. + */ + public void setSourceCollection(@Nullable final ObservableCollection sourceCollection) { + this.sourceCollection = sourceCollection != null ? sourceCollection : new ObservableList<>(); + update(); } /** @@ -70,8 +77,8 @@ public class ObservableFilteredList extends ObservableCollection { * @param sourceCollection Collection with items. */ public void setSourceCollection(@Nullable final Collection sourceCollection) { - this.sourceCollection = sourceCollection != null ? new ArrayList<>(sourceCollection) : null; - updateCollections(); + this.sourceCollection = sourceCollection != null ? new ObservableList<>(sourceCollection) : new ObservableList<>(); + update(); } /** @@ -81,73 +88,51 @@ public class ObservableFilteredList extends ObservableCollection { */ public void setFilter(@Nullable final Func1 filter) { this.filter = filter; - updateCollections(); + update(); } /** * Updates collection by current filter. Use it if some item's parameter which is important for filtering have changing. */ - public void updateCollections() { - if (sourceCollection == null) { - if (filteredList != null) { - final Change change = new Change<>(Change.Type.REMOVED, filteredList, 0); - filteredList = null; - notifyAboutChange(change); - } - return; - } - final List oldFilteredList = filteredList; - if (filter != null) { - filteredList = filterCollection(sourceCollection, filter); - } else { - filteredList = new ArrayList<>(sourceCollection); - } - if (oldFilteredList != null) { - final Collection> changes = Change.calculateCollectionChanges(oldFilteredList, filteredList, false); - if (!changes.isEmpty()) { - notifyAboutChanges(changes); - } - } else { - notifyAboutChange(new Change<>(Change.Type.INSERTED, filteredList, 0)); + private void update() { + if (sourceCollectionSubscription != null) { + sourceCollectionSubscription.unsubscribe(); + sourceCollectionSubscription = null; } + sourceCollectionSubscription = sourceCollection.observeItems() + .observeOn(Schedulers.computation()) + .subscribe(items -> { + final List oldFilteredList = filteredList; + filteredList = filterCollection(items, filter); + notifyAboutChanges(Change.calculateCollectionChanges(oldFilteredList, filteredList, false)); + }); } @Override public int size() { - return filteredList != null ? filteredList.size() : 0; + return filteredList.size(); } @NonNull @Override public TItem get(final int position) { - if (filteredList == null) { - throw new ShouldNotHappenException(); - } return filteredList.get(position); } @NonNull @Override public Collection getItems() { - return filteredList != null ? Collections.unmodifiableCollection(filteredList) : Collections.emptyList(); + return Collections.unmodifiableCollection(filteredList); } /** - * Returns source non-filtered collection of items. + * Returns source non-filtered observable collection of items. * * @return Non-filtered collection of items. */ @NonNull - public Collection getSourceItems() { - return sourceCollection != null ? Collections.unmodifiableCollection(sourceCollection) : Collections.emptyList(); - } - - @NonNull - @Override - public Observable loadItem(final int position) { - return filteredList != null && filteredList.size() > position - ? Observable.just(filteredList.get(position)) - : Observable.just(null); + public ObservableCollection getSourceCollection() { + return sourceCollection; } } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableList.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableList.java index 166d565..5610c64 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableList.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableList.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.List; import ru.touchin.roboswag.core.log.Lc; -import rx.Observable; /** * Created by Gavriil Sitnikov on 23/05/16. @@ -218,9 +217,7 @@ public class ObservableList extends ObservableCollection implement final Collection> changes = Change.calculateCollectionChanges(items, newItems, false); items.clear(); items.addAll(newItems); - if (!changes.isEmpty()) { - notifyAboutChanges(changes); - } + notifyAboutChanges(changes); } } @@ -243,14 +240,6 @@ public class ObservableList extends ObservableCollection implement } } - @NonNull - @Override - public Observable loadItem(final int position) { - synchronized (this) { - return position < items.size() ? Observable.just(items.get(position)) : Observable.just(null); - } - } - private void writeObject(@NonNull final ObjectOutputStream outputStream) throws IOException { outputStream.writeObject(items); } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadedRenewableItems.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadedRenewableItems.java deleted file mode 100644 index fc5105b..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadedRenewableItems.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.loadable; - -import android.support.annotation.Nullable; - -/** - * Created by Gavriil Sitnikov on 23/05/16. - * Object represents loaded items with reference to load other parts and info of are there more items to load or not. - * - * @param Type of items to load; - * @param Type of reference to load other parts of items; - * @param Type of reference to load newer parts of items. - */ -public interface LoadedRenewableItems extends LoadedItems { - - /** - * Returns count of new items other than loaded. - * - * @return Count of new items other than loaded. - */ - int getNewerItemsCount(); - - /** - * Returns reference to load newer items from this loaded part. - * - * @return Reference to load newer items. - */ - @Nullable - TNewerReference getNewerReference(); - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadingMoreList.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadingMoreList.java index 49b78be..38412ee 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadingMoreList.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadingMoreList.java @@ -24,6 +24,7 @@ import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.Executors; @@ -46,6 +47,7 @@ import rx.subjects.BehaviorSubject; * {@link ObservableCollection} which is loading items more and more by paging/limit-offset/reference-based mechanisms. * To use this collection {@link MoreItemsLoader} should be created. * {@link MoreItemsLoader} is an object to load next block of items by info from previous loaded block (last loaded item/reference etc.). + * Use {@link #loadItem(int)} and {@link #loadRange(int, int)} to load items asynchronously. * * @param Type of collection's items; * @param Type of reference object to help rightly loading next block of items; @@ -82,10 +84,7 @@ public class LoadingMoreList initialItems) { super(); this.loadingMoreObservable = Observable - .switchOnNext(Observable.>create(subscriber -> { - subscriber.onNext(createLoadRequestBasedObservable(this::createActualRequest, moreMoreItemsLoader::load)); - subscriber.onCompleted(); - })) + .switchOnNext(Observable.fromCallable(() -> createLoadRequestBasedObservable(this::createActualRequest, moreMoreItemsLoader::load))) .single() .doOnError(throwable -> { if (throwable instanceof IllegalArgumentException || throwable instanceof NoSuchElementException) { @@ -287,25 +286,50 @@ public class LoadingMoreList loadItem(final int position) { return Observable .switchOnNext(Observable - .>create(subscriber -> { + .fromCallable(() -> { if (position < size()) { - subscriber.onNext(Observable.just(get(position))); + return Observable.just(get(position)); } else if (moreItemsCount.getValue() == 0) { - subscriber.onNext(Observable.just((TItem) null)); + return Observable.just((TItem) null); } else { - subscriber.onNext(loadingMoreObservable.switchMap(ignored -> Observable.error(new NotLoadedYetException()))); + return loadingMoreObservable.switchMap(ignored -> Observable.error(new NotLoadedYetException())); } - subscriber.onCompleted(); }) .subscribeOn(loaderScheduler)) .retry((number, throwable) -> throwable instanceof NotLoadedYetException); } + /** + * Returns {@link Observable} which is loading item by range. + * It will return collection of loaded items in onNext callback. + * + * @param first First position of item to load; + * @param last Last position of item to load; + * @return {@link Observable} to load items. + */ + @NonNull + public Observable> loadRange(final int first, final int last) { + final List> itemsRequests = new ArrayList<>(); + for (int i = first; i <= last; i++) { + itemsRequests.add(loadItem(i)); + } + return Observable.concatEager(itemsRequests) + .filter(loadedItem -> loadedItem != null) + .toList() + .map(Collections::unmodifiableCollection); + } + /** * Remove all loaded items and resets collection's state. */ @@ -335,7 +359,7 @@ public class LoadingMoreList Type of collection's items; - * @param Type of reference object to help rightly loading next block of items; - * @param Type of reference object to help rightly loading block of newer items; - * @param Type of loading block of items. - */ -public class LoadingRenewableList> - extends LoadingMoreList { - - @Nullable - private TNewerReference newerReference; - @NonNull - private final BehaviorSubject newerItemsCount = BehaviorSubject.create(LoadedItems.UNKNOWN_ITEMS_COUNT); - - @NonNull - private final Observable loadingNewerObservable; - @NonNull - private final Observable loadingNewestObservable; - - public LoadingRenewableList(@NonNull final MoreItemsLoader moreMoreItemsLoader, - @NonNull final NewerItemsLoader newerItemsLoader) { - super(moreMoreItemsLoader); - this.loadingNewerObservable = createLoadingNewerObservable(newerItemsLoader, false); - this.loadingNewestObservable = createLoadingNewerObservable(newerItemsLoader, true); - } - - public LoadingRenewableList(@NonNull final MoreItemsLoader moreMoreItemsLoader, - @NonNull final NewerItemsLoader newerItemsLoader, - @Nullable final TLoadedItems initialItems) { - super(moreMoreItemsLoader, initialItems); - this.loadingNewerObservable = createLoadingNewerObservable(newerItemsLoader, false); - this.loadingNewestObservable = createLoadingNewerObservable(newerItemsLoader, true); - if (initialItems != null) { - updateNewerReference(initialItems); - } - } - - @NonNull - private Observable waitForInitialLoading(@NonNull final Observable observable) { - return getLoadingMoreObservable().ignoreElements().concatWith(observable); - } - - @NonNull - private NewerLoadRequest createActualRequest() { - return new NewerLoadRequest<>(newerReference, newerItemsCount.getValue()); - } - - @NonNull - private Observable createLoadingNewerObservable( - @NonNull final NewerItemsLoader newerItemsLoader, - final boolean renew) { - return Observable - .switchOnNext(Observable.>create(subscriber -> { - if (!renew) { - subscriber.onNext(Observable.concat( - //we need non-empty list to start loading newer items or we need to wait any change (should be insertion) - isEmpty() ? observeChanges().first().switchMap(ignored -> Observable.empty()) : Observable.empty(), - createLoadRequestBasedObservable(this::createActualRequest, - loadRequest -> loadRequest.getNewerReference() == null && isEmpty() - ? waitForInitialLoading(newerItemsLoader.load(loadRequest)) - : newerItemsLoader.load(loadRequest)))); - } else { - subscriber.onNext(newerItemsLoader.load(new NewerLoadRequest<>(null, LoadedItems.UNKNOWN_ITEMS_COUNT)) - .subscribeOn(Schedulers.io()) - .observeOn(getLoaderScheduler())); - } - subscriber.onCompleted(); - })) - .single() - .doOnError(throwable -> { - if (throwable instanceof IllegalArgumentException || throwable instanceof NoSuchElementException) { - Lc.assertion(new ShouldNotHappenException("Updates during loading not supported." - + " NewerItemsLoader should emit only one result.", throwable)); - } - }) - .doOnNext(loadedItems -> onNewerItemsLoaded(loadedItems, renew)) - .replay(1) - .refCount(); - } - - /** - * Returns if there are new items to load. - * - * @return True if there are more items to load. - */ - public boolean hasNewerItems() { - return newerItemsCount.getValue() != 0; - } - - /** - * Update a new items count. - * - * @param count new items count - */ - public void updateNewerItemsCount(final int count) { - newerItemsCount.onNext(count); - } - - /** - * Returns {@link Observable} which is providing status of if is there are new items to load or not. - * - * @return {@link Observable} of more items availability status. - */ - @NonNull - public Observable observeHasNewerItems() { - return newerItemsCount.map(count -> count != 0).distinctUntilChanged(); - } - - /** - * Returns {@link Observable} which is providing count of new items to load. - * - * @return {@link Observable} of new items availability status. - */ - @NonNull - public Observable observeNewerItemsCount() { - return newerItemsCount.distinctUntilChanged(); - } - - @Override - protected void onItemsLoaded(@NonNull final TLoadedItems loadedItems, final int insertPosition, final boolean reset) { - super.onItemsLoaded(loadedItems, insertPosition, reset); - if (newerReference == null) { - updateNewerReference(loadedItems); - } - } - - /** - * Calls when newer items part loaded. - * - * @param loadedItems Loaded items; - * @param renew Flag indicates is it loading just to load some new items (false) or to load totally new items (true). - */ - protected void onNewerItemsLoaded(@NonNull final TLoadedItems loadedItems, final boolean renew) { - onItemsLoaded(loadedItems, 0, renew); - updateNewerReference(loadedItems); - } - - @Override - protected void resetState() { - super.resetState(); - newerReference = null; - newerItemsCount.onNext(LoadedItems.UNKNOWN_ITEMS_COUNT); - } - - /** - * Returns {@link Observable} that will load newer items by count returned by last loaded items part. - * - * @return {@link Observable} to load newer items. - */ - @NonNull - public Observable loadNewer() { - return loadingNewerObservable.first(); - } - - /** - * Returns {@link Observable} that will load all newer items. - * - * @return Returns {@link Observable} to limited load newer items. - */ - @NonNull - public Observable loadToNewest() { - return loadToNewest(Integer.MAX_VALUE); - } - - /** - * Returns {@link Observable} that will load some newer itemslimited by maximum pages loading results. - * - * @param maxPageDeep Limit to load pages; - * @return Returns {@link Observable} to limited load newer items. - */ - @NonNull - public Observable loadToNewest(final int maxPageDeep) { - return Observable - .switchOnNext(Observable - .>create(subscriber -> { - subscriber.onNext(newerItemsCount.getValue() == 0 - ? Observable.empty() - : loadingNewerObservable.switchMap(ignored -> Observable.error(new NotLoadedYetException()))); - subscriber.onCompleted(); - }) - .subscribeOn(getLoaderScheduler())) - .retry((number, throwable) -> number <= maxPageDeep && throwable instanceof NotLoadedYetException); - } - - /** - * Returns {@link Observable} that tries to load some newer items even if there are no info about count of them. - * - * @return {@link Observable} to load newer items. - */ - @NonNull - public Observable renew() { - return loadingNewestObservable.first(); - } - - private void updateNewerReference(@NonNull final TLoadedItems loadedItems) { - if (loadedItems.getNewerReference() != null) { - newerReference = loadedItems.getNewerReference(); - } - newerItemsCount.onNext(loadedItems.getNewerItemsCount()); - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/NewerItemsLoader.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/NewerItemsLoader.java deleted file mode 100644 index 7ed9816..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/NewerItemsLoader.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.loadable; - -import android.support.annotation.NonNull; - -import rx.Observable; - -/** - * Created by Gavriil Sitnikov on 02/06/2016. - * Object that is loading new part of items by reference. - * - * @param Type of items to be loaded; - * @param Type of reference to be used to load new part of items; - * @param Type of loaded items part. - */ -public interface NewerItemsLoader> { - - @NonNull - Observable load(@NonNull final NewerLoadRequest newerLoadRequest); - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/NewerLoadRequest.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/NewerLoadRequest.java deleted file mode 100644 index 6566bd9..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/NewerLoadRequest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.loadable; - -import android.support.annotation.Nullable; - -import ru.touchin.roboswag.core.utils.ObjectUtils; - -/** - * Created by Gavriil Sitnikov on 02/06/2016. - * Request represents request to load new part of items. - * - * @param Type of reference to load new part of items. - */ -public class NewerLoadRequest { - - @Nullable - private final TNewerReference newerReference; - private final int newerItemsCount; - - public NewerLoadRequest(@Nullable final TNewerReference newerReference, final int newerItemsCount) { - this.newerReference = newerReference; - this.newerItemsCount = newerItemsCount; - } - - /** - * Returns reference to be used to load new part of items. - * - * @return Reference object. - */ - @Nullable - public TNewerReference getNewerReference() { - return newerReference; - } - - /** - * Count of newer items to load. - * - * @return Count of newer items to load. - */ - public int getNewerItemsCount() { - return newerItemsCount; - } - - @Override - public boolean equals(@Nullable final Object object) { - return object instanceof NewerLoadRequest - && ObjectUtils.equals(((NewerLoadRequest) object).newerReference, newerReference) - && ((NewerLoadRequest) object).newerItemsCount == newerItemsCount; - } - - @Override - public int hashCode() { - return newerItemsCount + (newerReference != null ? newerReference.hashCode() : 0); - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java new file mode 100644 index 0000000..00363e5 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * 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 ru.touchin.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.reflect.Type; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.log.LcGroup; +import ru.touchin.roboswag.core.observables.OnSubscribeRefCountWithCacheTime; +import ru.touchin.roboswag.core.utils.ObjectUtils; +import ru.touchin.roboswag.core.utils.Optional; +import rx.Completable; +import rx.Observable; +import rx.Scheduler; +import rx.Single; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Actions; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; + +/** + * Created by Gavriil Sitnikov on 04/10/2015. + * Base class allows to async access to some store. + * Supports conversion between store and actual value. If it is not needed then use {@link SameTypesConverter} + * Supports migration from specific version to latest by {@link Migration} object. + * Allows to set default value which will be returned if actual value is null. + * Allows to declare specific {@link ObserveStrategy}. + * Also specific {@link Scheduler} could be specified to not create new scheduler per storable. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}; + * @param Type of actual value operating by Storable. Could be same as {@link TObject}. + */ +public abstract class BaseStorable { + + public static final LcGroup STORABLE_LC_GROUP = new LcGroup("STORABLE"); + + private static final long DEFAULT_CACHE_TIME_MILLIS = TimeUnit.SECONDS.toMillis(5); + + @NonNull + private static ObserveStrategy getDefaultObserveStrategyFor(@NonNull final Type objectType, @NonNull final Type storeObjectType) { + if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) objectType)) { + return ObserveStrategy.CACHE_ACTUAL_VALUE; + } + if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) storeObjectType)) { + return ObserveStrategy.CACHE_STORE_VALUE; + } + return ObserveStrategy.NO_CACHE; + } + + @NonNull + private final TKey key; + @NonNull + private final Type objectType; + @NonNull + private final Type storeObjectType; + @NonNull + private final Store store; + @NonNull + private final Converter converter; + @NonNull + private final PublishSubject> newStoreValueEvent = PublishSubject.create(); + @NonNull + private final Observable> storeValueObservable; + @NonNull + private final Observable> valueObservable; + @NonNull + private final Scheduler scheduler; + + public BaseStorable(@NonNull final BuilderCore builderCore) { + this(builderCore.key, builderCore.objectType, builderCore.storeObjectType, + builderCore.store, builderCore.converter, builderCore.observeStrategy, + builderCore.migration, builderCore.defaultValue, builderCore.storeScheduler, builderCore.cacheTimeMillis); + } + + @SuppressWarnings("PMD.ExcessiveParameterList") + //ExcessiveParameterList: that's why we are using builder to create it + private BaseStorable(@NonNull final TKey key, + @NonNull final Type objectType, + @NonNull final Type storeObjectType, + @NonNull final Store store, + @NonNull final Converter converter, + @Nullable final ObserveStrategy observeStrategy, + @Nullable final Migration migration, + @Nullable final TObject defaultValue, + @Nullable final Scheduler storeScheduler, + final long cacheTimeMillis) { + this.key = key; + this.objectType = objectType; + this.storeObjectType = storeObjectType; + this.store = store; + this.converter = converter; + + final ObserveStrategy nonNullObserveStrategy + = observeStrategy != null ? observeStrategy : getDefaultObserveStrategyFor(objectType, storeObjectType); + scheduler = storeScheduler != null ? storeScheduler : Schedulers.from(Executors.newSingleThreadExecutor()); + storeValueObservable + = createStoreValueObservable(nonNullObserveStrategy, migration, defaultValue, cacheTimeMillis); + valueObservable = createValueObservable(storeValueObservable, nonNullObserveStrategy, cacheTimeMillis); + } + + @Nullable + private Optional returnDefaultValueIfNull(@NonNull final Optional storeObject, @Nullable final TObject defaultValue) { + if (storeObject.getValue() != null || defaultValue == null) { + return storeObject; + } + + try { + return new Optional<>(converter.toStoreObject(objectType, storeObjectType, defaultValue)); + } catch (final Converter.ConversionException exception) { + STORABLE_LC_GROUP.w(exception, "Exception while converting default value of '%s' from '%s' from store %s", + key, defaultValue, store); + throw OnErrorThrowable.from(exception); + } + } + + @NonNull + private Observable> createStoreInitialLoadingObservable(@Nullable final Migration migration) { + final Single> loadObservable = store.loadObject(storeObjectType, key) + .doOnError(throwable -> STORABLE_LC_GROUP.w(throwable, "Exception while trying to load value of '%s' from store %s", key, store)); + return (migration != null ? migration.migrateToLatestVersion(key).andThen(loadObservable) : loadObservable) + .subscribeOn(scheduler) + .observeOn(scheduler) + .toObservable() + .replay(1) + .refCount() + .take(1); + } + + @NonNull + private Observable> createStoreValueObservable(@NonNull final ObserveStrategy observeStrategy, + @Nullable final Migration migration, + @Nullable final TObject defaultValue, + final long cacheTimeMillis) { + final Observable> storeInitialLoadingObservable = createStoreInitialLoadingObservable(migration); + final Observable> result = storeInitialLoadingObservable + .concatWith(newStoreValueEvent) + .map(storeObject -> returnDefaultValueIfNull(storeObject, defaultValue)); + return observeStrategy == ObserveStrategy.CACHE_STORE_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE + ? Observable.unsafeCreate(new OnSubscribeRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS)) + : result; + } + + @NonNull + private Observable> createValueObservable(@NonNull final Observable> storeValueObservable, + @NonNull final ObserveStrategy observeStrategy, + final long cacheTimeMillis) { + final Observable> result = storeValueObservable + .map(storeObject -> { + try { + return new Optional<>(converter.toObject(objectType, storeObjectType, storeObject.getValue())); + } catch (final Converter.ConversionException exception) { + STORABLE_LC_GROUP.w(exception, "Exception while trying to converting value of '%s' from store %s by %s", + key, storeObject, store, converter); + throw OnErrorThrowable.from(exception); + } + }); + return observeStrategy == ObserveStrategy.CACHE_ACTUAL_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE + ? Observable.unsafeCreate(new OnSubscribeRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS)) + : result; + } + + /** + * Returns key of value. + * + * @return Unique key. + */ + @NonNull + public TKey getKey() { + return key; + } + + /** + * Returns type of actual object. + * + * @return Type of actual object. + */ + @NonNull + public Type getObjectType() { + return objectType; + } + + /** + * Returns type of store object. + * + * @return Type of store object. + */ + @NonNull + public Type getStoreObjectType() { + return storeObjectType; + } + + /** + * Returns {@link Store} where store class representation of object is storing. + * + * @return Store. + */ + @NonNull + public Store getStore() { + return store; + } + + /** + * Returns {@link Converter} to convert values from store class to actual and back. + * + * @return Converter. + */ + @NonNull + public Converter getConverter() { + return converter; + } + + @NonNull + private Completable internalSet(@Nullable final TObject newValue, final boolean checkForEqualityBeforeSet) { + return (checkForEqualityBeforeSet ? storeValueObservable.take(1).toSingle() : Single.just(new Optional<>(null))) + .observeOn(scheduler) + .flatMapCompletable(oldStoreValue -> { + final TStoreObject newStoreValue; + try { + newStoreValue = converter.toStoreObject(objectType, storeObjectType, newValue); + } catch (final Converter.ConversionException exception) { + STORABLE_LC_GROUP.w(exception, "Exception while trying to store value of '%s' from store %s by %s", + key, newValue, store, converter); + return Completable.error(exception); + } + if (checkForEqualityBeforeSet && ObjectUtils.equals(newStoreValue, oldStoreValue.getValue())) { + return Completable.complete(); + } + return store.storeObject(storeObjectType, key, newStoreValue) + .doOnError(throwable -> STORABLE_LC_GROUP.w(throwable, + "Exception while trying to store value of '%s' from store %s by %s", + key, newValue, store, converter)) + .observeOn(scheduler) + .andThen(Completable.fromAction(() -> { + newStoreValueEvent.onNext(new Optional<>(newStoreValue)); + if (checkForEqualityBeforeSet) { + STORABLE_LC_GROUP.i("Value of '%s' changed from '%s' to '%s'", key, oldStoreValue, newStoreValue); + } else { + STORABLE_LC_GROUP.i("Value of '%s' force changed to '%s'", key, newStoreValue); + } + })); + }); + } + + /** + * Creates observable which is async setting value to store. + * It is not checking if stored value equals new value. + * In result it will be faster to not get value from store and compare but it will emit item to {@link #observe()} subscribers. + * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! + * + * @param newValue Value to set; + * @return Observable of setting process. + */ + //COMPATIBILITY NOTE: it is not Completable to prevent migration of old code + @NonNull + public Observable forceSet(@Nullable final TObject newValue) { + return internalSet(newValue, false).toObservable(); + } + + /** + * Creates observable which is async setting value to store. + * It is checking if stored value equals new value. + * In result it will take time to get value from store and compare + * but it won't emit item to {@link #observe()} subscribers if stored value equals new value. + * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! + * + * @param newValue Value to set; + * @return Observable of setting process. + */ + //COMPATIBILITY NOTE: it is not Completable to prevent migration of old code + @NonNull + public Observable set(@Nullable final TObject newValue) { + return internalSet(newValue, true).toObservable(); + } + + @Deprecated + //COMPATIBILITY NOTE: it is deprecated as it's execution not bound to Android lifecycle objects + public void setCalm(@Nullable final TObject newValue) { + set(newValue).subscribe(Actions.empty(), Lc::assertion); + } + + /** + * Sets value synchronously. You should NOT use this method normally. Use {@link #set(Object)} asynchronously instead. + * + * @param newValue Value to set; + */ + @Deprecated + //deprecation: it should be used for debug only and in very rare cases. + public void setSync(@Nullable final TObject newValue) { + set(newValue).toBlocking().subscribe(); + } + + @NonNull + protected Observable> observeOptionalValue() { + return valueObservable; + } + + /** + * Returns Observable which is emitting item on subscribe and every time when someone have changed value. + * It could emit next and error events but not completed. + * + * @return Returns observable of value. + */ + @NonNull + public abstract Observable observe(); + + /** + * Returns Observable which is emitting only one item on subscribe. + * It could emit next and error events but not completed. + * + * @return Returns observable of value. + */ + @NonNull + //COMPATIBILITY NOTE: it is not Single to prevent migration of old code + public Observable get() { + return observe().take(1); + } + + /** + * Gets value synchronously. You should NOT use this method normally. Use {@link #get()} or {@link #observe()} asynchronously instead. + * + * @return Returns value; + */ + @Deprecated + //deprecation: it should be used for debug only and in very rare cases. + @Nullable + public TReturnObject getSync() { + return get().toBlocking().first(); + } + + /** + * Enum that is representing strategy of observing item from store. + */ + public enum ObserveStrategy { + + /** + * Not caching value so on every {@link #get()} emit it will get value from {@link #getStore()} and converts it with {@link #getConverter()}. + */ + NO_CACHE, + /** + * Caching only store value so on every {@link #get()} emit it will converts it with {@link #getConverter()}. + * Do not use such strategy if store object could be big (like byte-array of file). + */ + CACHE_STORE_VALUE, + /** + * Caching value so it won't spend time for getting value from {@link #getStore()} and converts it by {@link #getConverter()}. + * But it will take time for getting value from {@link #getStore()} to set value. + * Do not use such strategy if object could be big (like Bitmap or long string). + * Do not use such strategy if object is mutable because multiple subscribers could then change it's state. + */ + CACHE_ACTUAL_VALUE, + /** + * Caching value so it won't spend time for getting value from {@link #getStore()} and converts it by {@link #getConverter()}. + * It won't take time or getting value from {@link #getStore()} to set value. + * Do not use such strategy if store object could be big (like byte-array of file). + * Do not use such strategy if object could be big (like Bitmap or long string). + * Do not use such strategy if object is mutable because multiple subscribers could then change it's state. + */ + CACHE_STORE_AND_ACTUAL_VALUE + + } + + /** + * Helper class to create various builders. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}. + */ + public static class BuilderCore { + + @NonNull + protected final TKey key; + @NonNull + protected final Type objectType; + @NonNull + private final Type storeObjectType; + @NonNull + private final Store store; + @NonNull + private final Converter converter; + @Nullable + private ObserveStrategy observeStrategy; + @Nullable + private Migration migration; + @Nullable + private TObject defaultValue; + @Nullable + private Scheduler storeScheduler; + private long cacheTimeMillis; + + protected BuilderCore(@NonNull final TKey key, + @NonNull final Type objectType, + @NonNull final Type storeObjectType, + @NonNull final Store store, + @NonNull final Converter converter) { + this(key, objectType, storeObjectType, store, converter, null, null, null, null, DEFAULT_CACHE_TIME_MILLIS); + } + + protected BuilderCore(@NonNull final BuilderCore sourceBuilder) { + this(sourceBuilder.key, sourceBuilder.objectType, sourceBuilder.storeObjectType, + sourceBuilder.store, sourceBuilder.converter, sourceBuilder.observeStrategy, + sourceBuilder.migration, sourceBuilder.defaultValue, sourceBuilder.storeScheduler, sourceBuilder.cacheTimeMillis); + } + + @SuppressWarnings({"PMD.ExcessiveParameterList", "CPD-START"}) + //CPD: it is same code as constructor of Storable + //ExcessiveParameterList: that's why we are using builder to create it + private BuilderCore(@NonNull final TKey key, + @NonNull final Type objectType, + @NonNull final Type storeObjectType, + @NonNull final Store store, + @NonNull final Converter converter, + @Nullable final ObserveStrategy observeStrategy, + @Nullable final Migration migration, + @Nullable final TObject defaultValue, + @Nullable final Scheduler storeScheduler, + final long cacheTimeMillis) { + this.key = key; + this.objectType = objectType; + this.storeObjectType = storeObjectType; + this.store = store; + this.converter = converter; + this.observeStrategy = observeStrategy; + this.migration = migration; + this.defaultValue = defaultValue; + this.storeScheduler = storeScheduler; + this.cacheTimeMillis = cacheTimeMillis; + } + + @SuppressWarnings("CPD-END") + protected void setStoreSchedulerInternal(@Nullable final Scheduler storeScheduler) { + this.storeScheduler = storeScheduler; + } + + protected void setObserveStrategyInternal(@Nullable final ObserveStrategy observeStrategy) { + this.observeStrategy = observeStrategy; + } + + protected void setMigrationInternal(@NonNull final Migration migration) { + this.migration = migration; + } + + protected void setCacheTimeInternal(final long cacheTime, @NonNull final TimeUnit timeUnit) { + this.cacheTimeMillis = timeUnit.toMillis(cacheTime); + } + + @Nullable + protected TObject getDefaultValue() { + return defaultValue; + } + + protected void setDefaultValueInternal(@NonNull final TObject defaultValue) { + this.defaultValue = defaultValue; + } + + } + +} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java index 0f36f5e..f5adf78 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java @@ -58,7 +58,7 @@ public class Migration { @NonNull private Single loadCurrentVersion(@NonNull final TKey key) { return versionsStore.loadObject(Long.class, key) - .map(version -> version != null ? version : DEFAULT_VERSION) + .map(version -> version.getValue() != null ? version.getValue() : DEFAULT_VERSION) .onErrorResumeNext(throwable -> Single.error(new MigrationException(String.format("Can't get version of '%s' from %s", key, versionsStore), throwable))); } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java index 8796747..4a5a1f2 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java @@ -23,20 +23,12 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.lang.reflect.Type; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import ru.touchin.roboswag.core.log.LcGroup; -import ru.touchin.roboswag.core.observables.OnSubscribeRefCountWithCacheTime; -import ru.touchin.roboswag.core.observables.storable.builders.NonNullStorableBuilder; -import ru.touchin.roboswag.core.utils.ObjectUtils; -import rx.Completable; +import ru.touchin.roboswag.core.observables.storable.concrete.NonNullStorable; +import ru.touchin.roboswag.core.utils.Optional; import rx.Observable; import rx.Scheduler; -import rx.Single; -import rx.exceptions.OnErrorThrowable; -import rx.schedulers.Schedulers; -import rx.subjects.PublishSubject; /** * Created by Gavriil Sitnikov on 04/10/2015. @@ -51,419 +43,17 @@ import rx.subjects.PublishSubject; * @param Type of actual object; * @param Type of store object. Could be same as {@link TObject}. */ -public class Storable { - - public static final LcGroup STORABLE_LC_GROUP = new LcGroup("STORABLE"); - - private static final long DEFAULT_CACHE_TIME_MILLIS = TimeUnit.SECONDS.toMillis(5); - - @NonNull - private static ObserveStrategy getDefaultObserveStrategyFor(@NonNull final Type objectType, @NonNull final Type storeObjectType) { - if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) objectType)) { - return ObserveStrategy.CACHE_ACTUAL_VALUE; - } - if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) storeObjectType)) { - return ObserveStrategy.CACHE_STORE_VALUE; - } - return ObserveStrategy.NO_CACHE; - } - - @NonNull - private final TKey key; - @NonNull - private final Type objectType; - @NonNull - private final Type storeObjectType; - @NonNull - private final Store store; - @NonNull - private final Converter converter; - @NonNull - private final PublishSubject newStoreValueEvent = PublishSubject.create(); - @NonNull - private final Observable storeValueObservable; - @NonNull - private final Observable valueObservable; - @NonNull - private final Scheduler scheduler; +//COMPATIBILITY NOTE: in RxJava2 it should extends BaseStorable> +public class Storable extends BaseStorable { public Storable(@NonNull final BuilderCore builderCore) { - this(builderCore.key, builderCore.objectType, builderCore.storeObjectType, - builderCore.store, builderCore.converter, builderCore.observeStrategy, - builderCore.migration, builderCore.defaultValue, builderCore.storeScheduler, builderCore.cacheTimeMillis); - } - - @SuppressWarnings("PMD.ExcessiveParameterList") - //ExcessiveParameterList: that's why we are using builder to create it - private Storable(@NonNull final TKey key, - @NonNull final Type objectType, - @NonNull final Type storeObjectType, - @NonNull final Store store, - @NonNull final Converter converter, - @Nullable final ObserveStrategy observeStrategy, - @Nullable final Migration migration, - @Nullable final TObject defaultValue, - @Nullable final Scheduler storeScheduler, - final long cacheTimeMillis) { - this.key = key; - this.objectType = objectType; - this.storeObjectType = storeObjectType; - this.store = store; - this.converter = converter; - - final ObserveStrategy nonNullObserveStrategy - = observeStrategy != null ? observeStrategy : getDefaultObserveStrategyFor(objectType, storeObjectType); - scheduler = storeScheduler != null ? storeScheduler : Schedulers.from(Executors.newSingleThreadExecutor()); - storeValueObservable - = createStoreValueObservable(nonNullObserveStrategy, migration, defaultValue, cacheTimeMillis); - valueObservable = createValueObservable(storeValueObservable, nonNullObserveStrategy, cacheTimeMillis); - } - - @Nullable - private TStoreObject returnDefaultValueIfNull(@Nullable final TStoreObject storeObject, @Nullable final TObject defaultValue) { - if (storeObject != null || defaultValue == null) { - return storeObject; - } - - try { - return converter.toStoreObject(objectType, storeObjectType, defaultValue); - } catch (final Converter.ConversionException exception) { - STORABLE_LC_GROUP.w(exception, "Exception while converting default value of '%s' from '%s' from store %s", - key, defaultValue, store); - throw OnErrorThrowable.from(exception); - } + super(builderCore); } @NonNull - private Observable createStoreInitialLoadingObservable(@Nullable final Migration migration) { - final Single loadObservable = store.loadObject(storeObjectType, key) - .doOnError(throwable -> STORABLE_LC_GROUP.w(throwable, "Exception while trying to load value of '%s' from store %s", key, store)); - return (migration != null ? migration.migrateToLatestVersion(key).andThen(loadObservable) : loadObservable) - .subscribeOn(scheduler) - .observeOn(scheduler) - .toObservable() - .replay(1) - .refCount() - .take(1); - } - - @NonNull - private Observable createStoreValueObservable(@NonNull final ObserveStrategy observeStrategy, - @Nullable final Migration migration, - @Nullable final TObject defaultValue, - final long cacheTimeMillis) { - final Observable storeInitialLoadingObservable = createStoreInitialLoadingObservable(migration); - final Observable result = storeInitialLoadingObservable - .concatWith(newStoreValueEvent) - .map(storeObject -> returnDefaultValueIfNull(storeObject, defaultValue)); - return observeStrategy == ObserveStrategy.CACHE_STORE_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE - ? Observable.create(new OnSubscribeRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS)) - : result; - } - - @NonNull - private Observable createValueObservable(@NonNull final Observable storeValueObservable, - @NonNull final ObserveStrategy observeStrategy, - final long cacheTimeMillis) { - final Observable result = storeValueObservable - .map(storeObject -> { - try { - return converter.toObject(objectType, storeObjectType, storeObject); - } catch (final Converter.ConversionException exception) { - STORABLE_LC_GROUP.w(exception, "Exception while trying to converting value of '%s' from store %s by %s", - key, storeObject, store, converter); - throw OnErrorThrowable.from(exception); - } - }); - return observeStrategy == ObserveStrategy.CACHE_ACTUAL_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE - ? Observable.create(new OnSubscribeRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS)) - : result; - } - - /** - * Returns key of value. - * - * @return Unique key. - */ - @NonNull - public TKey getKey() { - return key; - } - - /** - * Returns type of actual object. - * - * @return Type of actual object. - */ - @NonNull - public Type getObjectType() { - return objectType; - } - - /** - * Returns type of store object. - * - * @return Type of store object. - */ - @NonNull - public Type getStoreObjectType() { - return storeObjectType; - } - - /** - * Returns {@link Store} where store class representation of object is storing. - * - * @return Store. - */ - @NonNull - public Store getStore() { - return store; - } - - /** - * Returns {@link Converter} to convert values from store class to actual and back. - * - * @return Converter. - */ - @NonNull - public Converter getConverter() { - return converter; - } - - @NonNull - private Completable internalSet(@Nullable final TObject newValue, final boolean checkForEqualityBeforeSet) { - return (checkForEqualityBeforeSet ? storeValueObservable.take(1) : Observable.just(null)) - .observeOn(scheduler) - .switchMap(oldStoreValue -> { - final TStoreObject newStoreValue; - try { - newStoreValue = converter.toStoreObject(objectType, storeObjectType, newValue); - } catch (final Converter.ConversionException exception) { - STORABLE_LC_GROUP.w(exception, "Exception while trying to store value of '%s' from store %s by %s", - key, newValue, store, converter); - return Observable.error(exception); - } - if (checkForEqualityBeforeSet && ObjectUtils.equals(newStoreValue, oldStoreValue)) { - return Observable.empty(); - } - return store.storeObject(storeObjectType, key, newStoreValue) - .doOnError(throwable -> STORABLE_LC_GROUP.w(throwable, - "Exception while trying to store value of '%s' from store %s by %s", - key, newValue, store, converter)) - .observeOn(scheduler) - .andThen(Completable.fromAction(() -> { - newStoreValueEvent.onNext(newStoreValue); - if (checkForEqualityBeforeSet) { - STORABLE_LC_GROUP.i("Value of '%s' changed from '%s' to '%s'", key, oldStoreValue, newStoreValue); - } else { - STORABLE_LC_GROUP.i("Value of '%s' force changed to '%s'", key, newStoreValue); - } - })) - .toObservable(); - }) - .toCompletable(); - } - - /** - * Creates observable which is async setting value to store. - * It is not checking if stored value equals new value. - * In result it will be faster to not get value from store and compare but it will emit item to {@link #observe()} subscribers. - * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! //TODO: it's Completable :( - * - * @param newValue Value to set; - * @return Observable of setting process. - */ - @NonNull - public Observable forceSet(@Nullable final TObject newValue) { - return internalSet(newValue, false).toObservable(); - } - - /** - * Creates observable which is async setting value to store. - * It is checking if stored value equals new value. - * In result it will take time to get value from store and compare - * but it won't emit item to {@link #observe()} subscribers if stored value equals new value. - * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! //TODO: it's Completable :( - * - * @param newValue Value to set; - * @return Observable of setting process. - */ - @NonNull - public Observable set(@Nullable final TObject newValue) { - return internalSet(newValue, true).toObservable(); - } - - /** - * Sets value synchronously. You should NOT use this method normally. Use {@link #set(Object)} asynchronously instead. - * - * @param newValue Value to set; - */ - @Deprecated - //deprecation: it should be used for debug only and in very rare cases. - public void setSync(@Nullable final TObject newValue) { - set(newValue).toBlocking().subscribe(); - } - - /** - * Returns Observable which is emitting item on subscribe and every time when someone have changed value. - * It could emit next and error events but not completed. - * - * @return Returns observable of value. - */ - @NonNull + @Override public Observable observe() { - return valueObservable; - } - - /** - * Returns Observable which is emitting only one item on subscribe. //TODO: it's Single :( - * It could emit next and error events but not completed. - * - * @return Returns observable of value. - */ - @NonNull - public Observable get() { - return valueObservable.take(1); - } - - /** - * Gets value synchronously. You should NOT use this method normally. Use {@link #get()} or {@link #observe()} asynchronously instead. - * - * @return Returns value; - */ - @Deprecated - //deprecation: it should be used for debug only and in very rare cases. - @Nullable - public TObject getSync() { - return get().toBlocking().first(); - } - - /** - * Enum that is representing strategy of observing item from store. - */ - public enum ObserveStrategy { - - /** - * Not caching value so on every {@link #get()} emit it will get value from {@link #getStore()} and converts it with {@link #getConverter()}. - */ - NO_CACHE, - /** - * Caching only store value so on every {@link #get()} emit it will converts it with {@link #getConverter()}. - * Do not use such strategy if store object could be big (like byte-array of file). - */ - CACHE_STORE_VALUE, - /** - * Caching value so it won't spend time for getting value from {@link #getStore()} and converts it by {@link #getConverter()}. - * But it will take time for getting value from {@link #getStore()} to set value. - * Do not use such strategy if object could be big (like Bitmap or long string). - * Do not use such strategy if object is mutable because multiple subscribers could then change it's state. - */ - CACHE_ACTUAL_VALUE, - /** - * Caching value so it won't spend time for getting value from {@link #getStore()} and converts it by {@link #getConverter()}. - * It won't take time or getting value from {@link #getStore()} to set value. - * Do not use such strategy if store object could be big (like byte-array of file). - * Do not use such strategy if object could be big (like Bitmap or long string). - * Do not use such strategy if object is mutable because multiple subscribers could then change it's state. - */ - CACHE_STORE_AND_ACTUAL_VALUE - - } - - /** - * Helper class to create various builders. - * - * @param Type of key to identify object; - * @param Type of actual object; - * @param Type of store object. Could be same as {@link TObject}. - */ - public static class BuilderCore { - - @NonNull - protected final TKey key; - @NonNull - protected final Type objectType; - @NonNull - private final Type storeObjectType; - @NonNull - private final Store store; - @NonNull - private final Converter converter; - @Nullable - private ObserveStrategy observeStrategy; - @Nullable - private Migration migration; - @Nullable - private TObject defaultValue; - @Nullable - private Scheduler storeScheduler; - private long cacheTimeMillis; - - protected BuilderCore(@NonNull final TKey key, - @NonNull final Type objectType, - @NonNull final Type storeObjectType, - @NonNull final Store store, - @NonNull final Converter converter) { - this(key, objectType, storeObjectType, store, converter, null, null, null, null, DEFAULT_CACHE_TIME_MILLIS); - } - - protected BuilderCore(@NonNull final BuilderCore sourceBuilder) { - this(sourceBuilder.key, sourceBuilder.objectType, sourceBuilder.storeObjectType, - sourceBuilder.store, sourceBuilder.converter, sourceBuilder.observeStrategy, - sourceBuilder.migration, sourceBuilder.defaultValue, sourceBuilder.storeScheduler, sourceBuilder.cacheTimeMillis); - } - - @SuppressWarnings({"PMD.ExcessiveParameterList", "CPD-START"}) - //CPD: it is same code as constructor of Storable - //ExcessiveParameterList: that's why we are using builder to create it - private BuilderCore(@NonNull final TKey key, - @NonNull final Type objectType, - @NonNull final Type storeObjectType, - @NonNull final Store store, - @NonNull final Converter converter, - @Nullable final ObserveStrategy observeStrategy, - @Nullable final Migration migration, - @Nullable final TObject defaultValue, - @Nullable final Scheduler storeScheduler, - final long cacheTimeMillis) { - this.key = key; - this.objectType = objectType; - this.storeObjectType = storeObjectType; - this.store = store; - this.converter = converter; - this.observeStrategy = observeStrategy; - this.migration = migration; - this.defaultValue = defaultValue; - this.storeScheduler = storeScheduler; - this.cacheTimeMillis = cacheTimeMillis; - } - - @SuppressWarnings("CPD-END") - protected void setStoreSchedulerInternal(@Nullable final Scheduler storeScheduler) { - this.storeScheduler = storeScheduler; - } - - protected void setObserveStrategyInternal(@Nullable final ObserveStrategy observeStrategy) { - this.observeStrategy = observeStrategy; - } - - protected void setMigrationInternal(@NonNull final Migration migration) { - this.migration = migration; - } - - protected void setCacheTimeInternal(final long cacheTime, @NonNull final TimeUnit timeUnit) { - this.cacheTimeMillis = timeUnit.toMillis(cacheTime); - } - - @Nullable - protected TObject getDefaultValue() { - return defaultValue; - } - - protected void setDefaultValueInternal(@NonNull final TObject defaultValue) { - this.defaultValue = defaultValue; - } - + return observeOptionalValue().map(Optional::getValue); } /** @@ -540,8 +130,8 @@ public class Storable { * @return Builder that allows to specify other fields. */ @NonNull - public NonNullStorableBuilder setDefaultValue(@NonNull final TObject defaultValue) { - return new NonNullStorableBuilder<>(this, defaultValue); + public NonNullStorable.Builder setDefaultValue(@NonNull final TObject defaultValue) { + return new NonNullStorable.Builder<>(this, defaultValue); } /** diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java index 610bb34..5a294a1 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java @@ -24,6 +24,7 @@ import android.support.annotation.Nullable; import java.lang.reflect.Type; +import ru.touchin.roboswag.core.utils.Optional; import rx.Completable; import rx.Single; @@ -64,6 +65,6 @@ public interface Store { * @return Object from store found by key; */ @NonNull - Single loadObject(@NonNull Type storeObjectType, @NonNull TKey key); + Single> loadObject(@NonNull Type storeObjectType, @NonNull TKey key); } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/builders/NonNullStorableBuilder.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/builders/NonNullStorableBuilder.java deleted file mode 100644 index ab5cfba..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/builders/NonNullStorableBuilder.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.storable.builders; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.concurrent.TimeUnit; - -import ru.touchin.roboswag.core.observables.storable.Migration; -import ru.touchin.roboswag.core.observables.storable.Storable; -import ru.touchin.roboswag.core.observables.storable.concrete.NonNullStorable; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import rx.Scheduler; - -/** - * Created by Gavriil Sitnikov on 15/05/2016. - * Builder that is already contains not null default value. - * - * @param Type of key to identify object; - * @param Type of actual object; - * @param Type of store object. Could be same as {@link TObject}. - */ -public class NonNullStorableBuilder extends Storable.BuilderCore { - - public NonNullStorableBuilder(@NonNull final Storable.Builder sourceBuilder, - @NonNull final TObject defaultValue) { - super(sourceBuilder); - setDefaultValueInternal(defaultValue); - } - - /** - * Sets specific {@link Scheduler} to store/load/convert values on it. - * - * @param storeScheduler Scheduler; - * @return Builder that allows to specify other fields. - */ - @NonNull - public NonNullStorableBuilder setStoreScheduler(@Nullable final Scheduler storeScheduler) { - setStoreSchedulerInternal(storeScheduler); - return this; - } - - /** - * Sets specific {@link Storable.ObserveStrategy} to cache value in memory in specific way. - * - * @param observeStrategy ObserveStrategy; - * @return Builder that allows to specify other fields. - */ - @NonNull - public NonNullStorableBuilder setObserveStrategy(@Nullable final Storable.ObserveStrategy observeStrategy) { - setObserveStrategyInternal(observeStrategy); - return this; - } - - /** - * Sets cache time for while value that cached by {@link #setObserveStrategy(Storable.ObserveStrategy)} - * will be in memory after everyone unsubscribe. - * It is important for example for cases when user switches between screens and hide/open app very fast. - * - * @param cacheTime Cache time value; - * @param timeUnit Cache time units. - * @return Builder that allows to specify other fields. - */ - @NonNull - public NonNullStorableBuilder setCacheTime(final long cacheTime, @NonNull final TimeUnit timeUnit) { - setCacheTimeInternal(cacheTime, timeUnit); - return this; - } - - /** - * Sets specific {@link Migration} to migrate values from specific version to latest version. - * - * @param migration Migration; - * @return Builder that allows to specify other fields. - */ - @NonNull - public NonNullStorableBuilder setMigration(@NonNull final Migration migration) { - setMigrationInternal(migration); - return this; - } - - /** - * Building {@link NonNullStorable} object. - * - * @return New {@link NonNullStorable}. - */ - @NonNull - public NonNullStorable build() { - if (getDefaultValue() == null) { - throw new ShouldNotHappenException(); - } - return new NonNullStorable<>(this); - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java index 16840a9..99491d3 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java @@ -20,10 +20,16 @@ package ru.touchin.roboswag.core.observables.storable.concrete; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import java.util.concurrent.TimeUnit; + +import ru.touchin.roboswag.core.observables.storable.BaseStorable; +import ru.touchin.roboswag.core.observables.storable.Migration; import ru.touchin.roboswag.core.observables.storable.Storable; -import ru.touchin.roboswag.core.observables.storable.builders.NonNullStorableBuilder; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; +import rx.Observable; +import rx.Scheduler; /** * Created by Gavriil Sitnikov on 04/10/2015. @@ -34,20 +40,109 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * @param Type of actual object; * @param Type of store object. Could be same as {@link TObject}. */ -public class NonNullStorable extends Storable { +public class NonNullStorable extends BaseStorable { - public NonNullStorable(@NonNull final NonNullStorableBuilder builderCore) { + public NonNullStorable(@NonNull final Builder builderCore) { super(builderCore); } @NonNull @Override - public TObject getSync() { - final TObject result = super.getSync(); - if (result == null) { - throw new ShouldNotHappenException(); - } - return result; + public Observable observe() { + return observeOptionalValue() + .map(optional -> { + if (optional.getValue() == null) { + throw new ShouldNotHappenException(); + } + return optional.getValue(); + }); } + /** + * Created by Gavriil Sitnikov on 15/05/2016. + * Builder that is already contains not null default value. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}. + */ + @SuppressWarnings("CPD-START") + //CPD: it is same code as Builder of Storable because it's methods returning this and can't be inherited + public static class Builder extends BuilderCore { + + public Builder(@NonNull final Storable.Builder sourceBuilder, + @NonNull final TObject defaultValue) { + super(sourceBuilder); + if (defaultValue == null) { + throw new ShouldNotHappenException(); + } + setDefaultValueInternal(defaultValue); + } + + /** + * Sets specific {@link Scheduler} to store/load/convert values on it. + * + * @param storeScheduler Scheduler; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setStoreScheduler(@Nullable final Scheduler storeScheduler) { + setStoreSchedulerInternal(storeScheduler); + return this; + } + + /** + * Sets specific {@link ObserveStrategy} to cache value in memory in specific way. + * + * @param observeStrategy ObserveStrategy; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setObserveStrategy(@Nullable final ObserveStrategy observeStrategy) { + setObserveStrategyInternal(observeStrategy); + return this; + } + + /** + * Sets cache time for while value that cached by {@link #setObserveStrategy(ObserveStrategy)} + * will be in memory after everyone unsubscribe. + * It is important for example for cases when user switches between screens and hide/open app very fast. + * + * @param cacheTime Cache time value; + * @param timeUnit Cache time units. + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setCacheTime(final long cacheTime, @NonNull final TimeUnit timeUnit) { + setCacheTimeInternal(cacheTime, timeUnit); + return this; + } + + /** + * Sets specific {@link Migration} to migrate values from specific version to latest version. + * + * @param migration Migration; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setMigration(@NonNull final Migration migration) { + setMigrationInternal(migration); + return this; + } + + /** + * Building {@link NonNullStorable} object. + * + * @return New {@link NonNullStorable}. + */ + @NonNull + @SuppressWarnings("CPD-END") + public NonNullStorable build() { + if (getDefaultValue() == null) { + throw new ShouldNotHappenException(); + } + return new NonNullStorable<>(this); + } + + } } diff --git a/src/main/java/ru/touchin/roboswag/core/utils/Optional.java b/src/main/java/ru/touchin/roboswag/core/utils/Optional.java new file mode 100644 index 0000000..4e07d25 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/core/utils/Optional.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * 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 ru.touchin.roboswag.core.utils; + +import android.support.annotation.Nullable; + +import java.io.Serializable; + +/** + * Created by Gavriil Sitnikov on 16/04/2017. + * Holds nullable objects inside. It is needed to implement RxJava2 non-null emitting logic. + * + * @param Type of object. + */ +public class Optional implements Serializable { + + private static final long serialVersionUID = 1L; + + @Nullable + private final T value; + + public Optional(@Nullable final T value) { + this.value = value; + } + + /** + * Returns holding nullable object. + * + * @return Holding object. + */ + @Nullable + public T getValue() { + return value; + } + + @Override + public boolean equals(@Nullable final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + + final Optional that = (Optional) object; + return ObjectUtils.equals(value, that.value); + } + + @Override + public int hashCode() { + return value != null ? value.hashCode() : 0; + } + +} diff --git a/src/main/java/ru/touchin/roboswag/core/utils/ServiceBinder.java b/src/main/java/ru/touchin/roboswag/core/utils/ServiceBinder.java index 7a26e85..bbba707 100644 --- a/src/main/java/ru/touchin/roboswag/core/utils/ServiceBinder.java +++ b/src/main/java/ru/touchin/roboswag/core/utils/ServiceBinder.java @@ -22,6 +22,7 @@ package ru.touchin.roboswag.core.utils; import android.app.Service; import android.os.Binder; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; /** * Created by Gavriil Sitnikov on 03/10/2015. @@ -47,4 +48,23 @@ public class ServiceBinder extends Binder { return service; } + @Override + public boolean equals(@Nullable final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + + final ServiceBinder that = (ServiceBinder) object; + + return ObjectUtils.equals(service, that.service); + } + + @Override + public int hashCode() { + return service.hashCode(); + } + } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java b/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java index 6efbcc7..bc3f3e2 100644 --- a/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java +++ b/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java @@ -21,8 +21,6 @@ package ru.touchin.roboswag.core.utils; import android.support.annotation.NonNull; -import rx.functions.Func0; - /** * Created by Gavriil Sitnikov on 13/11/2015. * Thread local value with specified creator of value per thread. @@ -30,24 +28,33 @@ import rx.functions.Func0; public class ThreadLocalValue extends ThreadLocal { @NonNull - private final Func0 creator; + private final Fabric fabric; - public ThreadLocalValue(@NonNull final NonNullFunc creator) { + public ThreadLocalValue(@NonNull final Fabric fabric) { super(); - this.creator = creator; + this.fabric = fabric; } @NonNull @Override protected T initialValue() { - return creator.call(); + return fabric.create(); } - public interface NonNullFunc extends Func0 { + /** + * Fabric of thread-local objects. + * + * @param Type of objects. + */ + public interface Fabric { + /** + * Creates object. + * + * @return new instance of object. + */ @NonNull - @Override - T call(); + T create(); } diff --git a/src/main/java/ru/touchin/roboswag/core/utils/pairs/HalfNullablePair.java b/src/main/java/ru/touchin/roboswag/core/utils/pairs/HalfNullablePair.java index 6e5fb84..4acec6b 100644 --- a/src/main/java/ru/touchin/roboswag/core/utils/pairs/HalfNullablePair.java +++ b/src/main/java/ru/touchin/roboswag/core/utils/pairs/HalfNullablePair.java @@ -81,9 +81,7 @@ public class HalfNullablePair implements Serializable { @Override public int hashCode() { - int result = first.hashCode(); - result = 31 * result + (second != null ? second.hashCode() : 0); - return result; + return ObjectUtils.hashCode(first, second); } } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/utils/pairs/NonNullPair.java b/src/main/java/ru/touchin/roboswag/core/utils/pairs/NonNullPair.java index f185c66..ee78bb7 100644 --- a/src/main/java/ru/touchin/roboswag/core/utils/pairs/NonNullPair.java +++ b/src/main/java/ru/touchin/roboswag/core/utils/pairs/NonNullPair.java @@ -81,9 +81,7 @@ public class NonNullPair implements Serializable { @Override public int hashCode() { - int result = first.hashCode(); - result = 31 * result + second.hashCode(); - return result; + return ObjectUtils.hashCode(first, second); } } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/utils/pairs/NullablePair.java b/src/main/java/ru/touchin/roboswag/core/utils/pairs/NullablePair.java index 21f630d..75b2431 100644 --- a/src/main/java/ru/touchin/roboswag/core/utils/pairs/NullablePair.java +++ b/src/main/java/ru/touchin/roboswag/core/utils/pairs/NullablePair.java @@ -85,9 +85,7 @@ public class NullablePair implements Serializable { //todo: mb @Override public int hashCode() { - int result = first.hashCode(); - result = 31 * result + second.hashCode(); - return result; + return ObjectUtils.hashCode(first, second); } } \ No newline at end of file From 0fdc4c693eed98c842cc9657dac6b358914aeb92 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Mon, 17 Apr 2017 00:06:32 +0300 Subject: [PATCH 2/5] optional now looks java-like style --- .../touchin/roboswag/core/observables/BaseChangeable.java | 2 +- .../ru/touchin/roboswag/core/observables/Changeable.java | 2 +- .../roboswag/core/observables/NonNullChangeable.java | 4 ++-- .../roboswag/core/observables/storable/BaseStorable.java | 6 +++--- .../roboswag/core/observables/storable/Migration.java | 2 +- .../roboswag/core/observables/storable/Storable.java | 2 +- .../core/observables/storable/concrete/NonNullStorable.java | 4 ++-- src/main/java/ru/touchin/roboswag/core/utils/Optional.java | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/core/observables/BaseChangeable.java b/src/main/java/ru/touchin/roboswag/core/observables/BaseChangeable.java index a4efc1c..86ca15d 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/BaseChangeable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/BaseChangeable.java @@ -71,7 +71,7 @@ public abstract class BaseChangeable implements Serializab */ @Nullable public TValue get() { - return valueSubject.getValue().getValue(); + return valueSubject.getValue().get(); } /** diff --git a/src/main/java/ru/touchin/roboswag/core/observables/Changeable.java b/src/main/java/ru/touchin/roboswag/core/observables/Changeable.java index e878f38..243009c 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/Changeable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/Changeable.java @@ -46,7 +46,7 @@ public class Changeable extends BaseChangeable { @Override //COMPATIBILITY NOTE: in RxJava2 it should be Observable> public Observable observe() { - return observeOptionalValue().map(Optional::getValue); + return observeOptionalValue().map(Optional::get); } } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/NonNullChangeable.java b/src/main/java/ru/touchin/roboswag/core/observables/NonNullChangeable.java index 85b54de..731913d 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/NonNullChangeable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/NonNullChangeable.java @@ -68,10 +68,10 @@ public class NonNullChangeable extends BaseChangeable { public Observable observe() { return observeOptionalValue() .map(optional -> { - if (optional.getValue() == null) { + if (optional.get() == null) { throw new ShouldNotHappenException(); } - return optional.getValue(); + return optional.get(); }); } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java index 00363e5..f4e2f30 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java @@ -124,7 +124,7 @@ public abstract class BaseStorable { @Nullable private Optional returnDefaultValueIfNull(@NonNull final Optional storeObject, @Nullable final TObject defaultValue) { - if (storeObject.getValue() != null || defaultValue == null) { + if (storeObject.get() != null || defaultValue == null) { return storeObject; } @@ -171,7 +171,7 @@ public abstract class BaseStorable { final Observable> result = storeValueObservable .map(storeObject -> { try { - return new Optional<>(converter.toObject(objectType, storeObjectType, storeObject.getValue())); + return new Optional<>(converter.toObject(objectType, storeObjectType, storeObject.get())); } catch (final Converter.ConversionException exception) { STORABLE_LC_GROUP.w(exception, "Exception while trying to converting value of '%s' from store %s by %s", key, storeObject, store, converter); @@ -246,7 +246,7 @@ public abstract class BaseStorable { key, newValue, store, converter); return Completable.error(exception); } - if (checkForEqualityBeforeSet && ObjectUtils.equals(newStoreValue, oldStoreValue.getValue())) { + if (checkForEqualityBeforeSet && ObjectUtils.equals(newStoreValue, oldStoreValue.get())) { return Completable.complete(); } return store.storeObject(storeObjectType, key, newStoreValue) diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java index f5adf78..714fb4d 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java @@ -58,7 +58,7 @@ public class Migration { @NonNull private Single loadCurrentVersion(@NonNull final TKey key) { return versionsStore.loadObject(Long.class, key) - .map(version -> version.getValue() != null ? version.getValue() : DEFAULT_VERSION) + .map(version -> version.get() != null ? version.get() : DEFAULT_VERSION) .onErrorResumeNext(throwable -> Single.error(new MigrationException(String.format("Can't get version of '%s' from %s", key, versionsStore), throwable))); } diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java index 4a5a1f2..d7f88a6 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java @@ -53,7 +53,7 @@ public class Storable extends BaseStorable observe() { - return observeOptionalValue().map(Optional::getValue); + return observeOptionalValue().map(Optional::get); } /** diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java index 99491d3..2b7b0aa 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java @@ -51,10 +51,10 @@ public class NonNullStorable extends BaseStorable observe() { return observeOptionalValue() .map(optional -> { - if (optional.getValue() == null) { + if (optional.get() == null) { throw new ShouldNotHappenException(); } - return optional.getValue(); + return optional.get(); }); } diff --git a/src/main/java/ru/touchin/roboswag/core/utils/Optional.java b/src/main/java/ru/touchin/roboswag/core/utils/Optional.java index 4e07d25..86a8493 100644 --- a/src/main/java/ru/touchin/roboswag/core/utils/Optional.java +++ b/src/main/java/ru/touchin/roboswag/core/utils/Optional.java @@ -46,7 +46,7 @@ public class Optional implements Serializable { * @return Holding object. */ @Nullable - public T getValue() { + public T get() { return value; } From 51298db384ef1d8868ad17023f34415b79abdfa2 Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 26 Apr 2017 15:47:48 +0300 Subject: [PATCH 3/5] rxjava 1.2.10 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e711b15..5dfbaaf 100644 --- a/build.gradle +++ b/build.gradle @@ -19,5 +19,5 @@ android { dependencies { provided 'com.android.support:support-annotations:25.3.1' provided 'io.reactivex:rxandroid:1.2.1' - provided 'io.reactivex:rxjava:1.2.9' + provided 'io.reactivex:rxjava:1.2.10' } From 6bf5110cedf00884017e1db4f2e37d3a24a86782 Mon Sep 17 00:00:00 2001 From: Anton Domnikov Date: Thu, 4 May 2017 13:14:51 +0300 Subject: [PATCH 4/5] update build tools version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5dfbaaf..4564bab 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 25 - buildToolsVersion '25.0.2' + buildToolsVersion '25.0.3' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 From 4aea2f100c8e2c6c94a8792f74961468c1e81440 Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 10 May 2017 21:17:28 +0300 Subject: [PATCH 5/5] rxjava version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4564bab..7e7dd58 100644 --- a/build.gradle +++ b/build.gradle @@ -19,5 +19,5 @@ android { dependencies { provided 'com.android.support:support-annotations:25.3.1' provided 'io.reactivex:rxandroid:1.2.1' - provided 'io.reactivex:rxjava:1.2.10' + provided 'io.reactivex:rxjava:1.3.0' }