storable classsesadded for a while
This commit is contained in:
parent
7f46dd2d7f
commit
eeb9a8483a
|
|
@ -0,0 +1,13 @@
|
|||
package roboswag.org.storable;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="roboswag.org.storable">
|
||||
|
||||
<application android:allowBackup="true" android:label="@string/app_name"
|
||||
android:supportsRtl="true">
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package roboswag.org.core;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.AbstractList;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import roboswag.org.core.exceptions.ObjectIsMutableException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public final class ObjectUtils {
|
||||
|
||||
private static final List<Class> IMMUTABLE_COLLECTIONS_TYPES
|
||||
= Arrays.asList(AbstractList.class, AbstractMap.class, AbstractSet.class);
|
||||
|
||||
public static boolean equals(@Nullable Object object1, @Nullable Object object2) {
|
||||
// copy of Arrays.deepEqualsElements
|
||||
|
||||
if (object1 == object2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (object1 == null || object2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> cl1 = object1.getClass().getComponentType();
|
||||
Class<?> cl2 = object2.getClass().getComponentType();
|
||||
|
||||
if (cl1 != cl2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cl1 == null) {
|
||||
return object1.equals(object2);
|
||||
}
|
||||
|
||||
/*
|
||||
* compare as arrays
|
||||
*/
|
||||
if (object1 instanceof Object[]) {
|
||||
return Arrays.deepEquals((Object[]) object1, (Object[]) object2);
|
||||
} else if (cl1 == int.class) {
|
||||
return Arrays.equals((int[]) object1, (int[]) object2);
|
||||
} else if (cl1 == char.class) {
|
||||
return Arrays.equals((char[]) object1, (char[]) object2);
|
||||
} else if (cl1 == boolean.class) {
|
||||
return Arrays.equals((boolean[]) object1, (boolean[]) object2);
|
||||
} else if (cl1 == byte.class) {
|
||||
return Arrays.equals((byte[]) object1, (byte[]) object2);
|
||||
} else if (cl1 == long.class) {
|
||||
return Arrays.equals((long[]) object1, (long[]) object2);
|
||||
} else if (cl1 == float.class) {
|
||||
return Arrays.equals((float[]) object1, (float[]) object2);
|
||||
} else if (cl1 == double.class) {
|
||||
return Arrays.equals((double[]) object1, (double[]) object2);
|
||||
} else {
|
||||
return Arrays.equals((short[]) object1, (short[]) object2);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkIfIsImmutable(@NonNull Class<?> objectClass) throws ObjectIsMutableException {
|
||||
checkIfIsImmutable(objectClass, false, new HashSet<>());
|
||||
}
|
||||
|
||||
private static void checkIfIsImmutable(@NonNull Class<?> objectClass,
|
||||
boolean isSuperclass,
|
||||
@NonNull Set<Class> checkedClasses)
|
||||
throws ObjectIsMutableException {
|
||||
|
||||
if (checkedClasses.contains(objectClass)) {
|
||||
return;
|
||||
}
|
||||
checkedClasses.add(objectClass);
|
||||
|
||||
if (objectClass.isArray()) {
|
||||
throw new ObjectIsMutableException(objectClass + " is array which is mutable");
|
||||
}
|
||||
|
||||
if (objectClass.isPrimitive() || objectClass.getSuperclass() == Number.class
|
||||
|| objectClass.isEnum() || objectClass == Boolean.class
|
||||
|| objectClass == String.class || objectClass == Object.class) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isImmutableCollection(objectClass, objectClass.getGenericSuperclass(), checkedClasses)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSuperclass
|
||||
&& (!Modifier.isFinal(objectClass.getModifiers())
|
||||
|| (objectClass.isMemberClass() && !Modifier.isStatic(objectClass.getModifiers())))) {
|
||||
throw new ObjectIsMutableException(objectClass + " is not final and static");
|
||||
}
|
||||
|
||||
for (Field field : objectClass.getDeclaredFields()) {
|
||||
if (!Modifier.isFinal(field.getModifiers())) {
|
||||
throw new ObjectIsMutableException("Field " + field.getName() + " of class " + objectClass + " is not final");
|
||||
}
|
||||
if (isImmutableCollection(field.getType(), field.getGenericType(), checkedClasses)) {
|
||||
continue;
|
||||
}
|
||||
checkIfIsImmutable(field.getType(), false, checkedClasses);
|
||||
}
|
||||
|
||||
if (objectClass.getSuperclass() != null) {
|
||||
checkIfIsImmutable(objectClass.getSuperclass(), true, checkedClasses);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isImmutableCollection(@NonNull Class<?> objectClass,
|
||||
@Nullable Type genericType,
|
||||
@NonNull Set<Class> checkedClasses)
|
||||
throws ObjectIsMutableException {
|
||||
for (Class<?> collectionClass : IMMUTABLE_COLLECTIONS_TYPES) {
|
||||
if (collectionClass != objectClass && collectionClass != objectClass.getSuperclass()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(genericType instanceof ParameterizedType)) {
|
||||
throw new ObjectIsMutableException(objectClass + " is immutable collection but generic type " + genericType + " is not ParameterizedType");
|
||||
}
|
||||
for (Type parameterType : ((ParameterizedType) genericType).getActualTypeArguments()) {
|
||||
if (!(parameterType instanceof Class)) {
|
||||
throw new ObjectIsMutableException(objectClass + " is immutable collection but generic parameterType " + parameterType + "is not ParameterizedType");
|
||||
}
|
||||
checkIfIsImmutable((Class) parameterType, false, checkedClasses);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ObjectUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package roboswag.org.core.exceptions;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public class ObjectIsMutableException extends Exception {
|
||||
|
||||
public ObjectIsMutableException(@NonNull String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package roboswag.org.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import roboswag.org.storable.exceptions.ConversionException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public interface Converter<TObject, TStoreObject> {
|
||||
|
||||
@Nullable
|
||||
TStoreObject toStoreObject(@NonNull Class<TObject> objectClass,
|
||||
@NonNull Class<TStoreObject> storeObjectClass,
|
||||
@Nullable TObject object) throws ConversionException;
|
||||
|
||||
@Nullable
|
||||
TObject toObject(@NonNull Class<TObject> objectClass,
|
||||
@NonNull Class<TStoreObject> storeObjectClass,
|
||||
@Nullable TStoreObject storeObject) throws ConversionException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package roboswag.org.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import roboswag.org.storable.exceptions.MigrationException;
|
||||
import roboswag.org.storable.exceptions.StoreException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 06/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public class Migration<TKey> {
|
||||
|
||||
private final long latestVersion;
|
||||
@NonNull
|
||||
private final Store<TKey, Long> versionsStore;
|
||||
@NonNull
|
||||
private final List<Migrator<TKey, ?, ?>> migrators = new ArrayList<>();
|
||||
|
||||
public Migration(@NonNull Store<TKey, Long> versionsStore, long latestVersion) {
|
||||
this.versionsStore = versionsStore;
|
||||
this.latestVersion = latestVersion;
|
||||
}
|
||||
|
||||
public void addMigrator(@NonNull Migrator<TKey, ?, ?> migrator) {
|
||||
migrators.add(migrator);
|
||||
}
|
||||
|
||||
public void migrateToLatestVersion(@NonNull TKey key) throws MigrationException {
|
||||
Long version;
|
||||
try {
|
||||
version = versionsStore.loadObject(Long.class, key);
|
||||
} catch (StoreException throwable) {
|
||||
throw new MigrationException("Version for key " + key + " is null", throwable);
|
||||
}
|
||||
|
||||
if (version == null) {
|
||||
version = Migrator.DEFAULT_VERSION;
|
||||
}
|
||||
|
||||
while (!version.equals(latestVersion)) {
|
||||
long oldVersion = version;
|
||||
boolean migrationTriggered = false;
|
||||
for (Migrator<TKey, ?, ?> migrator : migrators) {
|
||||
if (migrator.supportMigrationFor(version) && migrator.getOldStore().contains(key)) {
|
||||
version = migrator.migrate(key, version);
|
||||
migrationTriggered = true;
|
||||
}
|
||||
}
|
||||
if (oldVersion > version) {
|
||||
throw new MigrationException("Version downgraded from [" + oldVersion + "] to [" + version + "]");
|
||||
} else if (oldVersion == version) {
|
||||
if (migrationTriggered) {
|
||||
throw new MigrationException("Migration not changed version [" + version + "]");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (version > latestVersion) {
|
||||
throw new MigrationException("Version [" + version + "] is higher than latest version [" + latestVersion + "]");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
versionsStore.storeObject(Long.class, key, latestVersion);
|
||||
} catch (StoreException throwable) {
|
||||
throw new MigrationException("Storing version failed for " + key, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package roboswag.org.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import roboswag.org.storable.exceptions.MigrationException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 05/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public abstract class Migrator<TKey, TOldStoreObject, TNewStoreObject> {
|
||||
|
||||
public static final long DEFAULT_VERSION = -1L;
|
||||
|
||||
@NonNull
|
||||
private final Store<TKey, TOldStoreObject> oldStore;
|
||||
@NonNull
|
||||
private final Store<TKey, TNewStoreObject> newStore;
|
||||
|
||||
public Migrator(@NonNull Store<TKey, TOldStoreObject> oldStore,
|
||||
@NonNull Store<TKey, TNewStoreObject> newStore) {
|
||||
this.oldStore = oldStore;
|
||||
this.newStore = newStore;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Store<TKey, TOldStoreObject> getOldStore() {
|
||||
return oldStore;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Store<TKey, TNewStoreObject> getNewStore() {
|
||||
return newStore;
|
||||
}
|
||||
|
||||
public abstract boolean supportMigrationFor(long version);
|
||||
|
||||
public long migrate(TKey oldKey, long version) throws MigrationException {
|
||||
if (!supportMigrationFor(version)) {
|
||||
throw new MigrationException("Version " + version + " not supported by " + this);
|
||||
}
|
||||
return migrateInternal(oldKey);
|
||||
}
|
||||
|
||||
protected abstract long migrateInternal(TKey key) throws MigrationException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
package roboswag.org.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import roboswag.org.core.ObjectUtils;
|
||||
import roboswag.org.core.exceptions.ObjectIsMutableException;
|
||||
import roboswag.org.storable.exceptions.ConversionException;
|
||||
import roboswag.org.storable.exceptions.MigrationException;
|
||||
import roboswag.org.storable.exceptions.StoreException;
|
||||
import roboswag.org.storable.exceptions.ValidationException;
|
||||
import rx.Observable;
|
||||
import rx.functions.Actions;
|
||||
import rx.schedulers.Schedulers;
|
||||
import rx.subjects.PublishSubject;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO
|
||||
*/
|
||||
public abstract class Storable<TKey, TObject, TStoreObject> {
|
||||
|
||||
private static final String LOG_TAG = "Storable";
|
||||
|
||||
private static int globalLogLevel = Log.ERROR;
|
||||
private static boolean isInDebugMode = false;
|
||||
|
||||
public static void setLogLevel(int logLevel) {
|
||||
globalLogLevel = logLevel;
|
||||
}
|
||||
|
||||
public static void setDebugMode(boolean debugMode) {
|
||||
isInDebugMode = debugMode;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final String name;
|
||||
@NonNull
|
||||
private final TKey key;
|
||||
@NonNull
|
||||
private final Class<TObject> objectClass;
|
||||
@NonNull
|
||||
private final Class<TStoreObject> storeObjectClass;
|
||||
@NonNull
|
||||
private final Store<TKey, TStoreObject> store;
|
||||
@NonNull
|
||||
private final Converter<TObject, TStoreObject> converter;
|
||||
private final boolean cloneOnGet;
|
||||
@Nullable
|
||||
private final Migration<TKey> migration;
|
||||
@Nullable
|
||||
private final Validator<TObject> validator;
|
||||
@Nullable
|
||||
private final TObject defaultValue;
|
||||
|
||||
@NonNull
|
||||
private final PublishSubject<TObject> valueSubject = PublishSubject.create();
|
||||
@NonNull
|
||||
private final Observable<TObject> valueObservable = Observable.<TObject>create(subscriber -> {
|
||||
try {
|
||||
subscriber.onNext(get());
|
||||
} catch (Exception throwable) {
|
||||
if (globalLogLevel <= Log.ERROR) {
|
||||
Log.e(LOG_TAG, "Error during get: " + Log.getStackTraceString(throwable));
|
||||
}
|
||||
subscriber.onError(throwable);
|
||||
}
|
||||
}).subscribeOn(Schedulers.io())
|
||||
.concatWith(valueSubject)
|
||||
.replay(1).autoConnect();
|
||||
|
||||
@Nullable
|
||||
private CachedValue<TStoreObject> cachedStoreDefaultValue;
|
||||
@Nullable
|
||||
private CachedValue<TStoreObject> cachedStoreValue;
|
||||
@Nullable
|
||||
private CachedValue<TObject> cachedValue;
|
||||
|
||||
protected Storable(@NonNull String name,
|
||||
@NonNull TKey key,
|
||||
@NonNull Class<TObject> objectClass,
|
||||
@NonNull Class<TStoreObject> storeObjectClass,
|
||||
@NonNull Store<TKey, TStoreObject> store,
|
||||
@NonNull Converter<TObject, TStoreObject> converter,
|
||||
boolean cloneOnGet,
|
||||
@Nullable Migration<TKey> migration,
|
||||
@Nullable Validator<TObject> validator,
|
||||
@Nullable TObject defaultValue) {
|
||||
this.name = name;
|
||||
this.key = key;
|
||||
this.objectClass = objectClass;
|
||||
this.storeObjectClass = storeObjectClass;
|
||||
this.store = store;
|
||||
this.converter = converter;
|
||||
this.cloneOnGet = cloneOnGet;
|
||||
this.migration = migration;
|
||||
this.validator = validator;
|
||||
this.defaultValue = defaultValue;
|
||||
|
||||
if (isInDebugMode && !cloneOnGet) {
|
||||
try {
|
||||
ObjectUtils.checkIfIsImmutable(objectClass);
|
||||
} catch (ObjectIsMutableException throwable) {
|
||||
Log.w(LOG_TAG, Log.getStackTraceString(throwable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public TKey getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Store<TKey, TStoreObject> getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Converter<TObject, TStoreObject> getConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TObject getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Validator<TObject> getValidator() {
|
||||
return validator;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private CachedValue<TStoreObject> getCachedStoreDefaultValue() throws ConversionException {
|
||||
if (cachedStoreDefaultValue == null) {
|
||||
cachedStoreDefaultValue = new CachedValue<>(converter.toStoreObject(objectClass, storeObjectClass, defaultValue));
|
||||
}
|
||||
return cachedStoreDefaultValue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TStoreObject getStoreValue() throws StoreException, ConversionException, MigrationException {
|
||||
synchronized (this) {
|
||||
if (cachedStoreValue == null) {
|
||||
if (migration != null) {
|
||||
try {
|
||||
migration.migrateToLatestVersion(key);
|
||||
} catch (MigrationException throwable) {
|
||||
if (isInDebugMode) {
|
||||
throw throwable;
|
||||
} else if (globalLogLevel <= Log.ERROR) {
|
||||
Log.e(LOG_TAG, "Error during migration: " + Log.getStackTraceString(throwable));
|
||||
}
|
||||
}
|
||||
}
|
||||
TStoreObject storeObject = store.loadObject(storeObjectClass, key);
|
||||
cachedStoreValue = storeObject == null && defaultValue != null
|
||||
? getCachedStoreDefaultValue()
|
||||
: new CachedValue<>(storeObject);
|
||||
}
|
||||
|
||||
return cachedStoreValue.value;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TObject getDirectValue() throws StoreException, ConversionException {
|
||||
synchronized (this) {
|
||||
if (cachedValue == null) {
|
||||
TStoreObject storeObject = store.loadObject(storeObjectClass, key);
|
||||
cachedValue = storeObject == null && defaultValue != null
|
||||
? new CachedValue<>(defaultValue)
|
||||
: new CachedValue<>(converter.toObject(objectClass, storeObjectClass, storeObject));
|
||||
}
|
||||
return cachedValue.value;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TObject get() throws StoreException, ConversionException, MigrationException {
|
||||
synchronized (this) {
|
||||
if (cloneOnGet) {
|
||||
TStoreObject storeValue = getStoreValue();
|
||||
return storeValue != null ? converter.toObject(objectClass, storeObjectClass, storeValue) : null;
|
||||
} else {
|
||||
return getDirectValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCachedValue(@Nullable TObject value, @Nullable TStoreObject storeObject) throws ConversionException {
|
||||
cachedValue = null;
|
||||
cachedStoreValue = null;
|
||||
if (cloneOnGet) {
|
||||
cachedStoreValue = storeObject == null && defaultValue != null
|
||||
? getCachedStoreDefaultValue()
|
||||
: new CachedValue<>(storeObject);
|
||||
} else {
|
||||
cachedValue = storeObject == null && defaultValue != null
|
||||
? new CachedValue<>(defaultValue)
|
||||
: new CachedValue<>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void set(@Nullable TObject value)
|
||||
throws ValidationException, ConversionException, StoreException, MigrationException {
|
||||
synchronized (this) {
|
||||
if (validator != null) {
|
||||
validator.validate(value);
|
||||
}
|
||||
|
||||
TObject oldValue = null;
|
||||
if (!cloneOnGet && cachedValue != null) {
|
||||
oldValue = cachedValue.value;
|
||||
if (ObjectUtils.equals(oldValue, value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TStoreObject valueToStore = converter.toStoreObject(objectClass, storeObjectClass, value);
|
||||
try {
|
||||
TStoreObject storedValue = getStoreValue();
|
||||
if (ObjectUtils.equals(storedValue, valueToStore)) {
|
||||
return;
|
||||
}
|
||||
if (oldValue == null) {
|
||||
oldValue = converter.toObject(objectClass, storeObjectClass, storedValue);
|
||||
}
|
||||
} catch (Exception throwable) {
|
||||
// some invalid value in store
|
||||
if (globalLogLevel <= Log.WARN) {
|
||||
Log.w(LOG_TAG, "Can't get current store value: " + Log.getStackTraceString(throwable));
|
||||
}
|
||||
}
|
||||
|
||||
store.storeObject(storeObjectClass, key, valueToStore);
|
||||
updateCachedValue(value, valueToStore);
|
||||
onValueChanged(get(), oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAsync(@Nullable TObject value) {
|
||||
setObservable(value).subscribe(Actions.empty(), this::onSetError);
|
||||
}
|
||||
|
||||
private void onSetError(Throwable throwable) {
|
||||
if (globalLogLevel <= Log.ERROR) {
|
||||
Log.e(LOG_TAG, "Error during set: " + Log.getStackTraceString(throwable));
|
||||
}
|
||||
}
|
||||
|
||||
public Observable<?> setObservable(@Nullable TObject value) {
|
||||
return Observable.create(subscriber -> {
|
||||
try {
|
||||
set(value);
|
||||
} catch (Exception throwable) {
|
||||
if (globalLogLevel <= Log.ERROR) {
|
||||
Log.e(LOG_TAG, "Error during set: " + Log.getStackTraceString(throwable));
|
||||
}
|
||||
subscriber.onError(throwable);
|
||||
}
|
||||
subscriber.onCompleted();
|
||||
}).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Observable<TObject> observe() {
|
||||
return valueObservable;
|
||||
}
|
||||
|
||||
protected void onValueChanged(@Nullable TObject newValue, TObject oldValue) {
|
||||
valueSubject.onNext(newValue);
|
||||
if (globalLogLevel <= Log.INFO) {
|
||||
Log.w(LOG_TAG, "Value changed from '" + oldValue + "' to '" + newValue + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private class CachedValue<T> {
|
||||
|
||||
@Nullable
|
||||
private final T value;
|
||||
|
||||
private CachedValue(@Nullable T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package roboswag.org.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import roboswag.org.storable.exceptions.StoreException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public interface Store<TKey, TStoreObject> {
|
||||
|
||||
boolean contains(@NonNull TKey key);
|
||||
|
||||
void storeObject(@NonNull Class<TStoreObject> storeObjectClass,
|
||||
@NonNull TKey key,
|
||||
@Nullable TStoreObject storeObject) throws StoreException;
|
||||
|
||||
@Nullable
|
||||
TStoreObject loadObject(@NonNull Class<TStoreObject> storeObjectClass, @NonNull TKey key) throws StoreException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package roboswag.org.storable;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import roboswag.org.storable.exceptions.ValidationException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public interface Validator<T> {
|
||||
|
||||
void validate(@Nullable T value) throws ValidationException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package roboswag.org.storable.exceptions;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public class ConversionException extends Exception {
|
||||
|
||||
public ConversionException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ConversionException(@NonNull String message, @NonNull Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package roboswag.org.storable.exceptions;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 05/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public class MigrationException extends Exception {
|
||||
|
||||
public MigrationException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MigrationException(@NonNull String message, @NonNull Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package roboswag.org.storable.exceptions;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public class StoreException extends Exception {
|
||||
|
||||
public StoreException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public StoreException(@NonNull String message, @NonNull Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package roboswag.org.storable.exceptions;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* TODO: fill description
|
||||
*/
|
||||
public class ValidationException extends Exception {
|
||||
|
||||
public ValidationException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ValidationException(@NonNull String message, @NonNull Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Storable</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package roboswag.org.storable;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* To work on unit tests, switch the Test Artifact in the Build Variants view.
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue