storable bug fix + static fixes

This commit is contained in:
Gavriil Sitnikov 2017-03-09 01:55:03 +03:00
parent 2927215c57
commit 631cf157dd
8 changed files with 361 additions and 102 deletions

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
apply plugin: 'me.tatarka.retrolambda' apply plugin: 'me.tatarka.retrolambda'
android { android {
compileSdkVersion 24 compileSdkVersion 25
buildToolsVersion '25.0.2' buildToolsVersion '25.0.2'
compileOptions { compileOptions {
@ -12,11 +12,11 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 9 minSdkVersion 9
targetSdkVersion 24 targetSdkVersion 25
} }
} }
dependencies { dependencies {
provided 'com.android.support:support-annotations:24.2.1' provided 'com.android.support:support-annotations:25.2.0'
provided 'io.reactivex:rxandroid:1.2.1' provided 'io.reactivex:rxandroid:1.2.1'
} }

View File

@ -41,6 +41,8 @@ public class ConsoleLogProcessor extends LogProcessor {
} }
@Override @Override
@SuppressWarnings("WrongConstant")
//WrongConstant: level.getPriority() is not wrong constant!
public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level, public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level,
@NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) { @NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) {
final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : "")); final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : ""));
@ -50,8 +52,9 @@ public class ConsoleLogProcessor extends LogProcessor {
newline = newline != -1 ? newline : length; newline = newline != -1 ? newline : length;
do { do {
final int end = Math.min(newline, i + MAX_LOG_LENGTH); final int end = Math.min(newline, i + MAX_LOG_LENGTH);
//noinspection WrongConstant if (Log.isLoggable(tag, level.getPriority())) {
Log.println(level.getPriority(), tag, messageToLog.substring(i, end)); Log.println(level.getPriority(), tag, messageToLog.substring(i, end));
}
i = end; i = end;
} }
while (i < newline); while (i < newline);

View File

@ -264,7 +264,9 @@ public final class Lc {
public static void printStackTrace(@NonNull final String tag) { public static void printStackTrace(@NonNull final String tag) {
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
Log.d(tag, TextUtils.join("\n", Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.length))); if (Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, TextUtils.join("\n", Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.length)));
}
} }
private Lc() { private Lc() {

View File

@ -0,0 +1,212 @@
/**
* Copyright 2014 Netflix, Inc.
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
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;
import rx.subscriptions.CompositeSubscription;
import rx.subscriptions.Subscriptions;
/**
* Returns an observable sequence that stays connected to the source as long as
* there is at least one subscription to the observable sequence and also it stays connected
* for cache time after everyone unsubscribe.
*
* @param <T> the value type
*/
@SuppressWarnings({"PMD.AvoidUsingVolatile", "PMD.CompareObjectsWithEquals"})
//AvoidUsingVolatile,CompareObjectsWithEquals: from OnSubscribeRefCount code
public final class OnSubscribeRefCountWithCacheTime<T> implements OnSubscribe<T> {
@NonNull
private final ConnectableObservable<? extends T> source;
@NonNull
private volatile CompositeSubscription baseSubscription = new CompositeSubscription();
@NonNull
private final AtomicInteger subscriptionCount = new AtomicInteger(0);
@NonNull
private final Scheduler scheduler = Schedulers.computation();
private final long cacheTime;
@NonNull
private final TimeUnit cacheTimeUnit;
@Nullable
private Scheduler.Worker worker;
/**
* Use this lock for every subscription and disconnect action.
*/
@NonNull
private final ReentrantLock lock = new ReentrantLock();
public OnSubscribeRefCountWithCacheTime(@NonNull final ConnectableObservable<? extends T> source,
final long cacheTime, @NonNull final TimeUnit cacheTimeUnit) {
this.source = source;
this.cacheTime = cacheTime;
this.cacheTimeUnit = cacheTimeUnit;
}
@Override
public void call(@NonNull final Subscriber<? super T> subscriber) {
lock.lock();
if (subscriptionCount.incrementAndGet() == 1) {
if (worker != null) {
worker.unsubscribe();
worker = null;
}
final AtomicBoolean writeLocked = new AtomicBoolean(true);
try {
// need to use this overload of connect to ensure that
// baseSubscription is set in the case that source is a
// synchronous Observable
source.connect(onSubscribe(subscriber, writeLocked));
} finally {
// need to cover the case where the source is subscribed to
// outside of this class thus preventing the Action1 passed
// to source.connect above being called
if (writeLocked.get()) {
// Action1 passed to source.connect was not called
lock.unlock();
}
}
} else {
try {
// ready to subscribe to source so do it
doSubscribe(subscriber, baseSubscription);
} finally {
// release the read lock
lock.unlock();
}
}
}
@NonNull
private Action1<Subscription> onSubscribe(@NonNull final Subscriber<? super T> subscriber,
@NonNull final AtomicBoolean writeLocked) {
return new Action1<Subscription>() {
@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);
}
}
};
}
private void doSubscribe(@NonNull final Subscriber<? super T> subscriber, @NonNull final CompositeSubscription currentBase) {
// handle unsubscribing from the base subscription
subscriber.add(disconnect(currentBase));
source.unsafeSubscribe(new Subscriber<T>(subscriber) {
@Override
public void onError(@NonNull final Throwable throwable) {
cleanup();
subscriber.onError(throwable);
}
@Override
public void onNext(@Nullable final T item) {
subscriber.onNext(item);
}
@Override
public void onCompleted() {
cleanup();
subscriber.onCompleted();
}
private void cleanup() {
// on error or completion we need to unsubscribe the base subscription
// and set the subscriptionCount to 0
lock.lock();
try {
if (baseSubscription == currentBase) {
baseSubscription.unsubscribe();
baseSubscription = new CompositeSubscription();
subscriptionCount.set(0);
}
} finally {
lock.unlock();
}
}
});
}
@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);
}
} finally {
lock.unlock();
}
}
});
}
}

View File

@ -1,41 +0,0 @@
package ru.touchin.roboswag.core.observables;
import android.support.annotation.NonNull;
import java.util.concurrent.CountDownLatch;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import rx.Observable;
/**
* Created by Gavriil Sitnikov on 21/05/2016.
* Some helper methods to work with JavaRx.
*/
public final class RxUtils {
/**
* Subscribes to specific {@link Observable} and waits for it's onCompleted event
* and then returns {@link ObservableResult} with all collected items and errors during subscription.
* You should NOT use such method normally. It is safer than {@link Observable#toBlocking()} but it is also like a hack.
*
* @param observable {@link Observable} to be executed;
* @param <T> Type of {@link Observable}'s items;
* @return {@link ObservableResult} which contains all items and errors collected during execution.
*/
@NonNull
public static <T> ObservableResult<T> executeSync(@NonNull final Observable<T> observable) {
final ObservableResult<T> result = new ObservableResult<>();
final CountDownLatch waiter = new CountDownLatch(1);
observable.subscribe(result::onNext, result::onError, waiter::countDown);
try {
waiter.await();
} catch (final InterruptedException exception) {
throw new ShouldNotHappenException(exception);
}
return result;
}
private RxUtils() {
}
}

View File

@ -24,13 +24,12 @@ import android.support.annotation.Nullable;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import ru.touchin.roboswag.core.log.LcGroup; import ru.touchin.roboswag.core.log.LcGroup;
import ru.touchin.roboswag.core.observables.ObservableResult; import ru.touchin.roboswag.core.observables.OnSubscribeRefCountWithCacheTime;
import ru.touchin.roboswag.core.observables.RxUtils;
import ru.touchin.roboswag.core.observables.storable.builders.NonNullStorableBuilder; import ru.touchin.roboswag.core.observables.storable.builders.NonNullStorableBuilder;
import ru.touchin.roboswag.core.utils.ObjectUtils; import ru.touchin.roboswag.core.utils.ObjectUtils;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import rx.Completable; import rx.Completable;
import rx.Observable; import rx.Observable;
import rx.Scheduler; import rx.Scheduler;
@ -55,6 +54,8 @@ public class Storable<TKey, TObject, TStoreObject> {
public static final LcGroup STORABLE_LC_GROUP = new LcGroup("STORABLE"); public static final LcGroup STORABLE_LC_GROUP = new LcGroup("STORABLE");
private static final long DEFAULT_CACHE_TIME_MILLIS = TimeUnit.SECONDS.toMillis(5);
@NonNull @NonNull
private static ObserveStrategy getDefaultObserveStrategyFor(@NonNull final Type objectType, @NonNull final Type storeObjectType) { private static ObserveStrategy getDefaultObserveStrategyFor(@NonNull final Type objectType, @NonNull final Type storeObjectType) {
if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) objectType)) { if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) objectType)) {
@ -88,18 +89,21 @@ public class Storable<TKey, TObject, TStoreObject> {
public Storable(@NonNull final BuilderCore<TKey, TObject, TStoreObject> builderCore) { public Storable(@NonNull final BuilderCore<TKey, TObject, TStoreObject> builderCore) {
this(builderCore.key, builderCore.objectType, builderCore.storeObjectType, this(builderCore.key, builderCore.objectType, builderCore.storeObjectType,
builderCore.store, builderCore.converter, builderCore.observeStrategy, builderCore.store, builderCore.converter, builderCore.observeStrategy,
builderCore.migration, builderCore.defaultValue, builderCore.storeScheduler); builderCore.migration, builderCore.defaultValue, builderCore.storeScheduler, builderCore.cacheTimeMillis);
} }
public Storable(@NonNull final TKey key, @SuppressWarnings("PMD.ExcessiveParameterList")
@NonNull final Type objectType, //ExcessiveParameterList: that's why we are using builder to create it
@NonNull final Type storeObjectType, private Storable(@NonNull final TKey key,
@NonNull final Store<TKey, TStoreObject> store, @NonNull final Type objectType,
@NonNull final Converter<TObject, TStoreObject> converter, @NonNull final Type storeObjectType,
@Nullable final ObserveStrategy observeStrategy, @NonNull final Store<TKey, TStoreObject> store,
@Nullable final Migration<TKey> migration, @NonNull final Converter<TObject, TStoreObject> converter,
@Nullable final TObject defaultValue, @Nullable final ObserveStrategy observeStrategy,
@Nullable final Scheduler storeScheduler) { @Nullable final Migration<TKey> migration,
@Nullable final TObject defaultValue,
@Nullable final Scheduler storeScheduler,
final long cacheTimeMillis) {
this.key = key; this.key = key;
this.objectType = objectType; this.objectType = objectType;
this.storeObjectType = storeObjectType; this.storeObjectType = storeObjectType;
@ -109,8 +113,8 @@ public class Storable<TKey, TObject, TStoreObject> {
= observeStrategy != null ? observeStrategy : getDefaultObserveStrategyFor(objectType, storeObjectType); = observeStrategy != null ? observeStrategy : getDefaultObserveStrategyFor(objectType, storeObjectType);
scheduler = storeScheduler != null ? storeScheduler : Schedulers.from(Executors.newSingleThreadExecutor()); scheduler = storeScheduler != null ? storeScheduler : Schedulers.from(Executors.newSingleThreadExecutor());
storeValueObservable storeValueObservable
= createStoreValueObservable(nonNullObserveStrategy, migration, defaultValue); = createStoreValueObservable(nonNullObserveStrategy, migration, defaultValue, cacheTimeMillis);
valueObservable = createValueObservable(storeValueObservable, nonNullObserveStrategy); valueObservable = createValueObservable(storeValueObservable, nonNullObserveStrategy, cacheTimeMillis);
} }
@Nullable @Nullable
@ -134,7 +138,8 @@ public class Storable<TKey, TObject, TStoreObject> {
@NonNull @NonNull
private Observable<TStoreObject> createStoreValueObservable(@NonNull final ObserveStrategy observeStrategy, private Observable<TStoreObject> createStoreValueObservable(@NonNull final ObserveStrategy observeStrategy,
@Nullable final Migration<TKey> migration, @Nullable final Migration<TKey> migration,
@Nullable final TObject defaultValue) { @Nullable final TObject defaultValue,
final long cacheTimeMillis) {
final Observable<TStoreObject> result = (migration != null final Observable<TStoreObject> result = (migration != null
? migration.migrateToLatestVersion(key).subscribeOn(scheduler) ? migration.migrateToLatestVersion(key).subscribeOn(scheduler)
: Completable.complete()) : Completable.complete())
@ -149,13 +154,14 @@ public class Storable<TKey, TObject, TStoreObject> {
.concatWith(newStoreValueEvent) .concatWith(newStoreValueEvent)
.map(storeObject -> returnDefaultValueIfNull(storeObject, defaultValue)); .map(storeObject -> returnDefaultValueIfNull(storeObject, defaultValue));
return observeStrategy == ObserveStrategy.CACHE_STORE_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE return observeStrategy == ObserveStrategy.CACHE_STORE_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE
? result.replay(1).refCount() ? Observable.create(new OnSubscribeRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS))
: result; : result;
} }
@NonNull @NonNull
private Observable<TObject> createValueObservable(@NonNull final Observable<TStoreObject> storeValueObservable, private Observable<TObject> createValueObservable(@NonNull final Observable<TStoreObject> storeValueObservable,
@NonNull final ObserveStrategy observeStrategy) { @NonNull final ObserveStrategy observeStrategy,
final long cacheTimeMillis) {
final Observable<TObject> result = storeValueObservable final Observable<TObject> result = storeValueObservable
.switchMap(storeObject -> Observable .switchMap(storeObject -> Observable
.fromCallable(() -> converter.toObject(objectType, storeObjectType, storeObject)) .fromCallable(() -> converter.toObject(objectType, storeObjectType, storeObject))
@ -169,7 +175,7 @@ public class Storable<TKey, TObject, TStoreObject> {
} }
})); }));
return observeStrategy == ObserveStrategy.CACHE_ACTUAL_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE return observeStrategy == ObserveStrategy.CACHE_ACTUAL_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE
? result.replay(1).refCount() ? Observable.create(new OnSubscribeRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS))
: result; : result;
} }
@ -223,28 +229,24 @@ public class Storable<TKey, TObject, TStoreObject> {
return converter; return converter;
} }
/**
* Creates observable which is async setting value to store.
* 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 @NonNull
public Observable<?> set(@Nullable final TObject newValue) { private Completable internalSet(@Nullable final TObject newValue, final boolean checkForEqualityBeforeSet) {
return storeValueObservable return (checkForEqualityBeforeSet ? storeValueObservable.first() : Observable.just(null))
.first()
.switchMap(oldStoreValue -> Observable .switchMap(oldStoreValue -> Observable
.fromCallable(() -> converter.toStoreObject(objectType, storeObjectType, newValue)) .fromCallable(() -> converter.toStoreObject(objectType, storeObjectType, newValue))
.subscribeOn(scheduler) .subscribeOn(scheduler)
.switchMap(newStoreValue -> { .switchMap(newStoreValue -> {
if (ObjectUtils.equals(newStoreValue, oldStoreValue)) { if (checkForEqualityBeforeSet && ObjectUtils.equals(newStoreValue, oldStoreValue)) {
return Observable.empty(); return Observable.empty();
} }
return store.storeObject(storeObjectType, key, newStoreValue) return store.storeObject(storeObjectType, key, newStoreValue)
.doOnCompleted(() -> { .doOnCompleted(() -> {
newStoreValueEvent.onNext(newStoreValue); newStoreValueEvent.onNext(newStoreValue);
STORABLE_LC_GROUP.i("Value of '%s' changed from '%s' to '%s'", key, oldStoreValue, 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(); .toObservable();
})) }))
@ -255,23 +257,48 @@ public class Storable<TKey, TObject, TStoreObject> {
STORABLE_LC_GROUP.w(throwable, "Exception while trying to store value of '%s' from store %s by %s", STORABLE_LC_GROUP.w(throwable, "Exception while trying to store value of '%s' from store %s by %s",
key, newValue, store, converter); key, newValue, store, converter);
} }
}); })
.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. * Sets value synchronously. You should NOT use this method normally. Use {@link #set(Object)} asynchronously instead.
* *
* @param newValue Value to set; * @param newValue Value to set;
* @throws Converter.ConversionException Throws if {@link Converter} threw exception during conversion;
* @throws Migration.MigrationException Throws if {@link Migration} threw exception during migration.
*/ */
@Deprecated @Deprecated
//deprecation: it should be used for debug only and in very rare cases. //deprecation: it should be used for debug only and in very rare cases.
public void setSync(@Nullable final TObject newValue) throws Throwable { public void setSync(@Nullable final TObject newValue) {
final ObservableResult<?> setResult = RxUtils.executeSync(set(newValue)); set(newValue).toBlocking().subscribe();
if (setResult.getError() != null) {
throw setResult.getError();
}
} }
/** /**
@ -300,21 +327,12 @@ public class Storable<TKey, TObject, TStoreObject> {
* Gets value synchronously. You should NOT use this method normally. Use {@link #get()} or {@link #observe()} asynchronously instead. * Gets value synchronously. You should NOT use this method normally. Use {@link #get()} or {@link #observe()} asynchronously instead.
* *
* @return Returns value; * @return Returns value;
* @throws Converter.ConversionException Throws if {@link Converter} threw exception during conversion;
* @throws Migration.MigrationException Throws if {@link Migration} threw exception during migration.
*/ */
@Deprecated @Deprecated
//deprecation: it should be used for debug only and in very rare cases. //deprecation: it should be used for debug only and in very rare cases.
@Nullable @Nullable
public TObject getSync() throws Throwable { public TObject getSync() {
final ObservableResult<TObject> getResult = RxUtils.executeSync(get()); return get().toBlocking().first();
if (getResult.getError() != null) {
throw getResult.getError();
}
if (getResult.getItems().size() != 1) {
throw new ShouldNotHappenException();
}
return getResult.getItems().get(0);
} }
/** /**
@ -376,23 +394,25 @@ public class Storable<TKey, TObject, TStoreObject> {
private TObject defaultValue; private TObject defaultValue;
@Nullable @Nullable
private Scheduler storeScheduler; private Scheduler storeScheduler;
private long cacheTimeMillis;
protected BuilderCore(@NonNull final TKey key, protected BuilderCore(@NonNull final TKey key,
@NonNull final Type objectType, @NonNull final Type objectType,
@NonNull final Type storeObjectType, @NonNull final Type storeObjectType,
@NonNull final Store<TKey, TStoreObject> store, @NonNull final Store<TKey, TStoreObject> store,
@NonNull final Converter<TObject, TStoreObject> converter) { @NonNull final Converter<TObject, TStoreObject> converter) {
this(key, objectType, storeObjectType, store, converter, null, null, null, null); this(key, objectType, storeObjectType, store, converter, null, null, null, null, DEFAULT_CACHE_TIME_MILLIS);
} }
protected BuilderCore(@NonNull final BuilderCore<TKey, TObject, TStoreObject> sourceBuilder) { protected BuilderCore(@NonNull final BuilderCore<TKey, TObject, TStoreObject> sourceBuilder) {
this(sourceBuilder.key, sourceBuilder.objectType, sourceBuilder.storeObjectType, this(sourceBuilder.key, sourceBuilder.objectType, sourceBuilder.storeObjectType,
sourceBuilder.store, sourceBuilder.converter, sourceBuilder.observeStrategy, sourceBuilder.store, sourceBuilder.converter, sourceBuilder.observeStrategy,
sourceBuilder.migration, sourceBuilder.defaultValue, sourceBuilder.storeScheduler); sourceBuilder.migration, sourceBuilder.defaultValue, sourceBuilder.storeScheduler, sourceBuilder.cacheTimeMillis);
} }
@SuppressWarnings("CPD-START") @SuppressWarnings({"PMD.ExcessiveParameterList", "CPD-START"})
//CPD: it is same code as constructor of Storable //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, private BuilderCore(@NonNull final TKey key,
@NonNull final Type objectType, @NonNull final Type objectType,
@NonNull final Type storeObjectType, @NonNull final Type storeObjectType,
@ -401,7 +421,8 @@ public class Storable<TKey, TObject, TStoreObject> {
@Nullable final ObserveStrategy observeStrategy, @Nullable final ObserveStrategy observeStrategy,
@Nullable final Migration<TKey> migration, @Nullable final Migration<TKey> migration,
@Nullable final TObject defaultValue, @Nullable final TObject defaultValue,
@Nullable final Scheduler storeScheduler) { @Nullable final Scheduler storeScheduler,
final long cacheTimeMillis) {
this.key = key; this.key = key;
this.objectType = objectType; this.objectType = objectType;
this.storeObjectType = storeObjectType; this.storeObjectType = storeObjectType;
@ -411,6 +432,7 @@ public class Storable<TKey, TObject, TStoreObject> {
this.migration = migration; this.migration = migration;
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
this.storeScheduler = storeScheduler; this.storeScheduler = storeScheduler;
this.cacheTimeMillis = cacheTimeMillis;
} }
@SuppressWarnings("CPD-END") @SuppressWarnings("CPD-END")
@ -426,6 +448,10 @@ public class Storable<TKey, TObject, TStoreObject> {
this.migration = migration; this.migration = migration;
} }
protected void setCacheTimeInternal(final long cacheTime, @NonNull final TimeUnit timeUnit) {
this.cacheTimeMillis = timeUnit.toMillis(cacheTime);
}
@Nullable @Nullable
protected TObject getDefaultValue() { protected TObject getDefaultValue() {
return defaultValue; return defaultValue;
@ -478,6 +504,20 @@ public class Storable<TKey, TObject, TStoreObject> {
return this; 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<TKey, TObject, TStoreObject> 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. * Sets specific {@link Migration} to migrate values from specific version to latest version.
* *

View File

@ -20,11 +20,15 @@
package ru.touchin.roboswag.core.observables.storable.builders; package ru.touchin.roboswag.core.observables.storable.builders;
import android.support.annotation.NonNull; 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.Migration;
import ru.touchin.roboswag.core.observables.storable.Storable; import ru.touchin.roboswag.core.observables.storable.Storable;
import ru.touchin.roboswag.core.observables.storable.concrete.NonNullStorable; import ru.touchin.roboswag.core.observables.storable.concrete.NonNullStorable;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException; import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import rx.Scheduler;
/** /**
* Created by Gavriil Sitnikov on 15/05/2016. * Created by Gavriil Sitnikov on 15/05/2016.
@ -42,6 +46,45 @@ public class NonNullStorableBuilder<TKey, TObject, TStoreObject> extends Storabl
setDefaultValueInternal(defaultValue); 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<TKey, TObject, TStoreObject> 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<TKey, TObject, TStoreObject> 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<TKey, TObject, TStoreObject> 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. * Sets specific {@link Migration} to migrate values from specific version to latest version.
* *

View File

@ -42,7 +42,7 @@ public class NonNullStorable<TKey, TObject, TStoreObject> extends Storable<TKey,
@NonNull @NonNull
@Override @Override
public TObject getSync() throws Throwable { public TObject getSync() {
final TObject result = super.getSync(); final TObject result = super.getSync();
if (result == null) { if (result == null) {
throw new ShouldNotHappenException(); throw new ShouldNotHappenException();