diff --git a/mockrealm/src/main/java/info/juanmendez/mockrealm/decorators/RealmModelDecorator.java b/mockrealm/src/main/java/info/juanmendez/mockrealm/decorators/RealmModelDecorator.java
index 48949fb..b0d92ed 100644
--- a/mockrealm/src/main/java/info/juanmendez/mockrealm/decorators/RealmModelDecorator.java
+++ b/mockrealm/src/main/java/info/juanmendez/mockrealm/decorators/RealmModelDecorator.java
@@ -80,6 +80,7 @@ public static RealmModel decorate(RealmModel realmModel ){
if( realmModel instanceof RealmObject ){
RealmObjectDecorator.handleDeleteActions( (RealmObject) realmModel);
RealmObjectDecorator.handleAsyncMethods( (RealmObject) realmModel);
+ setValid( realmModel, true );
}
return realmModel;
diff --git a/mockrealm/src/main/res/values/strings.xml b/mockrealm/src/main/res/values/strings.xml
index 49fc91e..ac631f2 100644
--- a/mockrealm/src/main/res/values/strings.xml
+++ b/mockrealm/src/main/res/values/strings.xml
@@ -1,3 +1,3 @@
- library
+ Mocking Realm
diff --git a/proguard-rules.pro b/proguard-rules.pro
new file mode 100644
index 0000000..63c7067
--- /dev/null
+++ b/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\musta\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0507c52
--- /dev/null
+++ b/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/main/java/info/juanmendez/mockrealm/MockRealm.java b/src/main/java/info/juanmendez/mockrealm/MockRealm.java
new file mode 100644
index 0000000..e7a7bff
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/MockRealm.java
@@ -0,0 +1,62 @@
+package info.juanmendez.mockrealm;
+
+import info.juanmendez.mockrealm.decorators.RealmConfigurationDecorator;
+import info.juanmendez.mockrealm.decorators.RealmDecorator;
+import info.juanmendez.mockrealm.decorators.RealmListDecorator;
+import info.juanmendez.mockrealm.decorators.RealmModelDecorator;
+import info.juanmendez.mockrealm.decorators.RealmObjectDecorator;
+import info.juanmendez.mockrealm.dependencies.RealmStorage;
+import info.juanmendez.mockrealm.models.RealmAnnotation;
+import io.realm.Realm;
+import io.realm.RealmConfiguration;
+import io.realm.RealmList;
+import io.realm.RealmObject;
+import io.realm.RealmQuery;
+import io.realm.RealmResults;
+
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+/**
+ * Created by @juanmendezinfo on 2/15/2017.
+ */
+public class MockRealm {
+
+ /**
+ * This is a method required in order to start up
+ * testing.
+ * @throws Exception
+ */
+ public static void prepare() throws Exception {
+ mockStatic( RealmList.class );
+ mockStatic( Realm.class );
+ mockStatic( RealmConfiguration.class);
+ mockStatic( RealmQuery.class );
+ mockStatic( RealmResults.class );
+ mockStatic( RealmObject.class );
+
+ RealmListDecorator.prepare();
+ RealmModelDecorator.prepare();
+ RealmObjectDecorator.prepare();
+ RealmDecorator.prepare();
+ RealmConfigurationDecorator.prepare();
+ }
+
+ /**
+ * Make sure to include each of your class annotation references through RealmAnnotation
+ * before testing each type of realmModel in your project
+ * @param annotations
+ */
+ public static void addAnnotations(RealmAnnotation ... annotations ){
+ for( RealmAnnotation annotation: annotations ){
+ RealmStorage.addAnnotations( annotation );
+ }
+ }
+
+ /**
+ * Call this method each time you want to clear your realm entries;
+ * specially, when starting a new test.
+ */
+ public static void clearData(){
+ RealmStorage.clear();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/decorators/RealmConfigurationDecorator.java b/src/main/java/info/juanmendez/mockrealm/decorators/RealmConfigurationDecorator.java
new file mode 100644
index 0000000..7c10745
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/decorators/RealmConfigurationDecorator.java
@@ -0,0 +1,51 @@
+package info.juanmendez.mockrealm.decorators;
+
+import java.io.File;
+
+import io.realm.RealmConfiguration;
+import io.realm.RealmMigration;
+import io.realm.rx.RxObservableFactory;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.anyVararg;
+import static org.powermock.api.mockito.PowerMockito.doAnswer;
+import static org.powermock.api.mockito.PowerMockito.doReturn;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+/**
+ * Created by Juan Mendez on 2/23/2017.
+ * www.juanmendez.info
+ * contact@juanmendez.info
+ */
+
+public class RealmConfigurationDecorator {
+
+
+ public static void prepare() throws Exception {
+
+ RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class);
+
+ // make a mockery of our inner class
+ RealmConfiguration.Builder mockedBuilder = mock(RealmConfiguration.Builder.class);
+
+ // magically return the mock when a new instance is required
+ whenNew(RealmConfiguration.Builder.class).withNoArguments().thenAnswer(invocation -> mockedBuilder);
+ whenNew(RealmConfiguration.Builder.class).withAnyArguments().thenAnswer(invocation -> mockedBuilder);
+ doAnswer(invocation -> mockRealmConfig ).when(mockedBuilder ).build();
+
+ //what to do with builder configs
+ doReturn(mockedBuilder).when( mockedBuilder ).name(anyString());
+ doReturn(mockedBuilder).when( mockedBuilder ).directory(any(File.class));
+ doReturn(mockedBuilder).when( mockedBuilder ).encryptionKey(any());
+ doReturn(mockedBuilder).when( mockedBuilder ).schemaVersion(anyLong());
+ doReturn(mockedBuilder).when( mockedBuilder ).migration(any(RealmMigration.class));
+ doReturn(mockedBuilder).when( mockedBuilder ).deleteRealmIfMigrationNeeded();
+ doReturn(mockedBuilder).when( mockedBuilder ).inMemory();
+ doReturn(mockedBuilder).when( mockedBuilder ).modules(any(), anyVararg());
+ doReturn(mockedBuilder).when( mockedBuilder ).rxFactory(any(RxObservableFactory.class));
+ doReturn(mockedBuilder).when( mockedBuilder ).assetFile(anyString());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/decorators/RealmDecorator.java b/src/main/java/info/juanmendez/mockrealm/decorators/RealmDecorator.java
new file mode 100644
index 0000000..d22e4a0
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/decorators/RealmDecorator.java
@@ -0,0 +1,383 @@
+package info.juanmendez.mockrealm.decorators;
+
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+import java.util.concurrent.Callable;
+
+import info.juanmendez.mockrealm.dependencies.Compare;
+import info.juanmendez.mockrealm.dependencies.RealmMatchers;
+import info.juanmendez.mockrealm.dependencies.RealmStorage;
+import info.juanmendez.mockrealm.dependencies.TransactionObservable;
+import info.juanmendez.mockrealm.models.Query;
+import info.juanmendez.mockrealm.models.TransactionEvent;
+import info.juanmendez.mockrealm.utils.QueryTracker;
+import info.juanmendez.mockrealm.utils.RealmModelUtil;
+import io.realm.Realm;
+import io.realm.RealmAsyncTask;
+import io.realm.RealmConfiguration;
+import io.realm.RealmList;
+import io.realm.RealmModel;
+import io.realm.RealmQuery;
+import rx.Observable;
+import rx.Scheduler;
+import rx.functions.Func0;
+import rx.schedulers.Schedulers;
+
+import static org.mockito.Matchers.any;
+import static org.powermock.api.mockito.PowerMockito.doAnswer;
+import static org.powermock.api.mockito.PowerMockito.doNothing;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+/**
+ * Created by @juanmendezinfo on 2/15/2017.
+ */
+public class RealmDecorator {
+
+ /**
+ * Only stick to Schedulers.immediate() for now. I tried others but they don't seem to work well
+ * in Robolectric
+ */
+ private static Scheduler observerScheduler = Schedulers.immediate();
+ private static Scheduler subscriberScheduler = Schedulers.immediate();
+
+ public static Realm prepare() throws Exception {
+
+ Realm realm = mock(Realm.class );
+ prepare(realm);
+ handleAsyncTransactions(realm);
+ handleSyncTransactions(realm);
+ return realm;
+ }
+
+ public static Scheduler getTransactionScheduler() {
+ return observerScheduler;
+ }
+
+ public static Scheduler getResponseScheduler() {
+ return subscriberScheduler;
+ }
+
+ private static void prepare(Realm realm) throws Exception {
+
+ doNothing().when( Realm.class, "init", any());
+
+ when( Realm.deleteRealm( any(RealmConfiguration.class))).thenReturn( true );
+
+ HashMap> realmMap = RealmStorage.getRealmMap();
+
+ when(Realm.getDefaultInstance()).thenReturn(realm);
+
+ when( Realm.deleteRealm(any(RealmConfiguration.class))).thenAnswer(invocation -> {
+ RealmStorage.clear();
+ return null;
+ });
+
+ doAnswer(new Answer() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ return null;
+ }
+ }).when(Realm.class, "setDefaultConfiguration", any() );
+
+ when( realm.createObject( Mockito.argThat(new RealmMatchers.ClassMatcher<>(RealmModel.class)) ) ).thenAnswer(invocation -> {
+ Class clazz = (Class) invocation.getArguments()[0];
+
+ if( !realmMap.containsKey(clazz)){
+ realmMap.put(clazz, RealmListDecorator.create());
+ }
+
+ RealmModel realmModel = RealmModelDecorator.create(clazz, true);
+ RealmStorage.addModel( realmModel );
+
+ return realmModel;
+ });
+
+ //realm.copyToRealm( realmModel )
+ when( realm.copyToRealm(Mockito.any( RealmModel.class ))).thenAnswer( new Answer(){
+
+ @Override
+ public RealmModel answer(InvocationOnMock invocationOnMock) throws Throwable {
+
+ RealmModel newRealmModel = (RealmModel) invocationOnMock.getArguments()[0];
+ return createOrUpdate( newRealmModel );
+ }
+ });
+
+ //realm.copyToRealmOrUpdate( realmModel ) same as realm.copyToRealm( realmModel )
+ when( realm.copyToRealmOrUpdate(Mockito.any( RealmModel.class ))).thenAnswer( new Answer(){
+
+ @Override
+ public RealmModel answer(InvocationOnMock invocationOnMock) throws Throwable {
+
+ RealmModel newRealmModel = (RealmModel) invocationOnMock.getArguments()[0];
+ return createOrUpdate( newRealmModel );
+ }
+ });
+
+ when( realm.where( Mockito.argThat( new RealmMatchers.ClassMatcher<>(RealmModel.class)) ) ).then(new Answer(){
+
+
+ @Override
+ public RealmQuery answer(InvocationOnMock invocationOnMock) throws Throwable {
+
+ //clear list being queried
+ Class clazz = (Class) invocationOnMock.getArguments()[0];
+ QueryTracker queryTracker = new QueryTracker(clazz);
+
+ RealmQuery realmQuery = RealmQueryDecorator.create(queryTracker);
+
+ if( !realmMap.containsKey(clazz))
+ {
+ realmMap.put(clazz, new RealmList<>());
+ }
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.startTopGroup).setArgs(new Object[]{realmMap.get(clazz)}) );
+
+
+ return realmQuery;
+ }
+ });
+ }
+
+ private static RealmModel createOrUpdate( RealmModel newRealmModel ){
+ HashMap> realmMap = RealmStorage.getRealmMap();
+ Class clazz = RealmModelUtil.getClass(newRealmModel);
+
+ if( !realmMap.containsKey(clazz)){
+ realmMap.put(clazz, RealmListDecorator.create());
+ }
+
+ RealmModel updatedRealmModel = RealmModelUtil.tryToUpdate( newRealmModel );
+
+ if( updatedRealmModel != null ){
+ return updatedRealmModel;
+ }
+
+ newRealmModel = RealmModelDecorator.decorate( newRealmModel );
+ RealmStorage.addModel( newRealmModel );
+ return newRealmModel;
+ }
+
+ private static void handleAsyncTransactions(Realm realm ){
+
+ //call execute() in Realm.Transaction object received.
+ doAnswer( new Answer() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+
+ if( invocation.getArguments().length > 0 ){
+ Realm.Transaction transaction = (Realm.Transaction) invocation.getArguments()[0];
+
+ queueTransaction(() -> {
+ transaction.execute( realm );
+ return null;
+ });
+ }
+ return null;
+ }
+ }).when( realm ).executeTransaction(any( Realm.Transaction.class ));
+
+ doAnswer( new Answer() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+
+ if( invocation.getArguments().length > 0 ){
+
+ queueTransaction( () -> {
+ Observable.fromCallable(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ Realm.Transaction transaction = (Realm.Transaction) invocation.getArguments()[0];
+ transaction.execute( realm );
+ return null;
+ }
+ })
+ .subscribeOn(getTransactionScheduler())
+ .observeOn( getResponseScheduler() ).subscribe(aVoid -> {});
+
+ return null;
+ });
+
+ }
+
+ return null;
+ }
+ }).when( realm ).executeTransactionAsync(any( Realm.Transaction.class ));
+
+
+ when( realm.executeTransactionAsync(any( Realm.Transaction.class ), any( Realm.Transaction.OnSuccess.class )) ).thenAnswer(
+ new Answer() {
+
+ @Override
+ public RealmAsyncTask answer(InvocationOnMock invocation) throws Throwable {
+
+ Realm.Transaction transaction = (Realm.Transaction) invocation.getArguments()[0];
+
+ return queueTransaction(() -> {
+
+ Observable.fromCallable(new Callable() {
+ @Override
+ public Boolean call() throws Exception {
+ if( invocation.getArguments().length >=1 ){
+ transaction.execute( realm );
+ return true;
+ }
+ return false;
+ }
+ })
+ .subscribeOn(getTransactionScheduler())
+ .observeOn( getResponseScheduler() )
+ .subscribe(aBoolean -> {
+ if( aBoolean && invocation.getArguments().length >=2 ){
+ Realm.Transaction.OnSuccess onSuccess = (Realm.Transaction.OnSuccess) invocation.getArguments()[1];
+ onSuccess.onSuccess();
+ }
+ });
+
+ return null;
+ });
+ }
+ }
+ );
+
+
+ when( realm.executeTransactionAsync(any( Realm.Transaction.class ), any( Realm.Transaction.OnSuccess.class ), any(Realm.Transaction.OnError.class)) ).thenAnswer(
+ new Answer() {
+
+ @Override
+ public RealmAsyncTask answer(InvocationOnMock invocation) throws Throwable {
+
+ return queueTransaction(() -> {
+ Observable.fromCallable(new Callable() {
+ @Override
+ public Boolean call() throws Exception {
+ if( invocation.getArguments().length >=1 ){
+ Realm.Transaction transaction = (Realm.Transaction) invocation.getArguments()[0];
+ transaction.execute(realm);
+ return true;
+ }
+
+ return false;
+ }
+ })
+ .subscribeOn(observerScheduler)
+ .observeOn( subscriberScheduler )
+ .subscribe(aBoolean -> {
+ if( aBoolean && invocation.getArguments().length >=2 ){
+ Realm.Transaction.OnSuccess onSuccess = (Realm.Transaction.OnSuccess) invocation.getArguments()[1];
+ onSuccess.onSuccess();
+ }
+
+ }, throwable -> {
+
+ if( invocation.getArguments().length >=3 ){
+ Realm.Transaction.OnError onError = (Realm.Transaction.OnError) invocation.getArguments()[2];
+ onError.onError(throwable);
+ }
+
+ });
+
+ return null;
+ });
+ }
+ }
+ );
+
+
+ when( realm.executeTransactionAsync(any( Realm.Transaction.class ), any(Realm.Transaction.OnError.class)) ).thenAnswer(
+ new Answer() {
+
+ @Override
+ public RealmAsyncTask answer(InvocationOnMock invocation) throws Throwable {
+ Realm.Transaction transaction = (Realm.Transaction) invocation.getArguments()[0];
+
+ return queueTransaction(() -> {
+
+ Observable.fromCallable(new Callable() {
+ @Override
+ public Boolean call() throws Exception {
+ if( invocation.getArguments().length >=1 ){
+
+
+ return true;
+ }
+
+ return false;
+ }
+ })
+ .subscribeOn(observerScheduler)
+ .observeOn( subscriberScheduler )
+ .subscribe(aBoolean -> {
+ if( aBoolean && invocation.getArguments().length >=2 ){
+ Realm.Transaction.OnSuccess onSuccess = (Realm.Transaction.OnSuccess) invocation.getArguments()[1];
+ onSuccess.onSuccess();
+ }
+
+ }, throwable -> {
+
+ if( invocation.getArguments().length >=2 ){
+ Realm.Transaction.OnError onError = (Realm.Transaction.OnError) invocation.getArguments()[2];
+ onError.onError(throwable);
+ }
+
+ });
+
+ return null;
+ });
+ }
+ }
+ );
+ }
+
+ private static void handleSyncTransactions(Realm realm ){
+
+ TransactionObservable.KeyTransaction transaction = new TransactionObservable.KeyTransaction( realm.toString() );
+
+ doAnswer(invocation -> {
+ TransactionObservable.startRequest(transaction);
+ return null;
+ }).when( realm ).beginTransaction();
+
+
+ doAnswer(invocation -> {
+ TransactionObservable.endRequest(transaction);
+ return null;
+ }).when( realm ).commitTransaction();
+ }
+
+ /**
+ * Parameter funk serves as a key to TransactionObservable.startRequest()
+ * @param funk code to execute when TransactionObservable allows it.
+ * @return RealmAsyncTask uses funk key to cancel transaction, or check if it has been canceled
+ */
+ private static RealmAsyncTask queueTransaction(Func0 funk){
+
+ TransactionObservable.startRequest(funk,
+ TransactionObservable.asObservable()
+ .filter(transactionEvent -> {
+ return transactionEvent.getState()== TransactionEvent.START_TRANSACTION && transactionEvent.getTarget() == funk;
+ })
+ .subscribe(o -> {
+ funk.call();
+ TransactionObservable.endRequest(funk);
+ })
+ );
+
+
+ return new RealmAsyncTask() {
+ @Override
+ public void cancel() {
+ TransactionObservable.cancel(funk);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return TransactionObservable.isCanceled( funk );
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/decorators/RealmListDecorator.java b/src/main/java/info/juanmendez/mockrealm/decorators/RealmListDecorator.java
new file mode 100644
index 0000000..e67379a
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/decorators/RealmListDecorator.java
@@ -0,0 +1,41 @@
+package info.juanmendez.mockrealm.decorators;
+
+import info.juanmendez.mockrealm.models.RealmListStubbed;
+import io.realm.RealmList;
+import io.realm.RealmModel;
+
+import static org.mockito.Matchers.anyVararg;
+import static org.powermock.api.mockito.PowerMockito.spy;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+
+/**
+ * Created by Juan Mendez on 2/25/2017.
+ * www.juanmendez.info
+ * contact@juanmendez.info
+ */
+public class RealmListDecorator {
+
+ public static void prepare() throws Exception {
+
+ spy(RealmList.class);
+
+ whenNew( RealmList.class ).withArguments(anyVararg()).thenAnswer(invocation -> {
+
+ RealmList realmList = create();
+ Object[] args = invocation.getArguments();
+
+ for (Object arg:args) {
+ realmList.add((RealmModel)arg);
+ }
+
+ return realmList;
+ });
+ }
+
+ public static RealmList create(){
+
+ return new RealmListStubbed();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/decorators/RealmModelDecorator.java b/src/main/java/info/juanmendez/mockrealm/decorators/RealmModelDecorator.java
new file mode 100644
index 0000000..48949fb
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/decorators/RealmModelDecorator.java
@@ -0,0 +1,196 @@
+package info.juanmendez.mockrealm.decorators;
+
+import org.powermock.reflect.Whitebox;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Set;
+
+import info.juanmendez.mockrealm.dependencies.RealmObservable;
+import info.juanmendez.mockrealm.dependencies.RealmStorage;
+import info.juanmendez.mockrealm.models.RealmEvent;
+import info.juanmendez.mockrealm.utils.RealmModelUtil;
+import io.realm.RealmList;
+import io.realm.RealmModel;
+import io.realm.RealmObject;
+
+import static org.powermock.api.mockito.PowerMockito.doReturn;
+import static org.powermock.api.mockito.PowerMockito.spy;
+
+/**
+ * Created by Juan Mendez on 2/24/2017.
+ * www.juanmendez.info
+ * contact@juanmendez.info
+ */
+
+public class RealmModelDecorator {
+
+ public static void prepare(){
+ }
+
+ private static RealmModel createFromClass( Class clazz ){
+
+ Constructor constructor = null;
+ RealmModel realmModel = null;
+
+ try {
+ constructor = clazz.getConstructor();
+ realmModel = (RealmModel) constructor.newInstance();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+
+ return realmModel;
+ }
+
+ public static RealmModel create(Class clazz, Boolean valid ) {
+ RealmModel realmModel = createFromClass( clazz );
+
+ if( realmModel instanceof RealmObject){
+ realmModel = RealmModelDecorator.decorate( realmModel );
+ }
+
+ setValid( realmModel, valid );
+ setLoaded( realmModel, valid );
+ return realmModel;
+ }
+
+ public static RealmModel decorate(RealmModel realmModel ){
+
+ Class clazz = RealmModelUtil.getClass( realmModel );
+
+ //only decorate new realmModels
+ if(RealmStorage.getRealmMap().get(clazz).contains( realmModel)){
+ return realmModel;
+ }
+
+ if( realmModel instanceof RealmObject ){
+ realmModel = spy( realmModel );
+ }
+
+ startDeleteObservers( realmModel);
+
+ if( realmModel instanceof RealmObject ){
+ RealmObjectDecorator.handleDeleteActions( (RealmObject) realmModel);
+ RealmObjectDecorator.handleAsyncMethods( (RealmObject) realmModel);
+ }
+
+ return realmModel;
+ }
+
+ /**
+ * Through RealmObservable be notified of changes, and see if any other realmModel deleted
+ * is referenced by this realmModel and remove such reference.
+ * @param realmModel
+ */
+ private static void startDeleteObservers(RealmModel realmModel ){
+
+ Set fieldSet = Whitebox.getAllInstanceFields(realmModel);
+ Class fieldClass;
+
+ /**
+ * There is one observable per each member observed either it's a realmModel or a realmResult
+ */
+ for (Field field: fieldSet) {
+
+ fieldClass = field.getType();
+
+ if( RealmModel.class.isAssignableFrom(fieldClass) ){
+
+ RealmModel finalRealmModel = realmModel;
+ RealmObservable.add( realmModel,
+
+ RealmObservable.asObservable()
+ .filter(realmEvent -> realmEvent.getState()== RealmEvent.MODEL_REMOVED)
+ .map(realmEvent -> realmEvent.getRealmModel())
+ .ofType(fieldClass)
+ .subscribe( o -> {
+ Object variable = Whitebox.getInternalState(finalRealmModel,
+ field.getName());
+
+ if( variable != null && variable == o ){
+ Whitebox.setInternalState(finalRealmModel,
+ field.getName(), (Object[]) null);
+ }
+ })
+ );
+ }
+ else if( fieldClass == RealmList.class ){
+
+ //RealmResults are not filtered
+
+ RealmObservable.add( realmModel,
+
+ RealmObservable.asObservable()
+ .filter(realmEvent -> realmEvent.getState() == RealmEvent.MODEL_REMOVED)
+ .map(realmEvent -> realmEvent.getRealmModel())
+ .subscribe(o -> {
+ RealmList realmList = (RealmList) Whitebox.getInternalState(realmModel, field.getName());
+
+ if( realmList != null ){
+ while( realmList.contains( o ) ){
+ realmList.remove( o );
+ }
+ }
+ })
+ );
+ }
+ }
+ }
+
+ /**
+ * mark either realmModel or realmObject as valid or not
+ * @param realmModel
+ * @param flag
+ */
+ public static void setValid(RealmModel realmModel, Boolean flag ){
+
+ if( realmModel instanceof RealmObject ){
+ doReturn( flag ).when( (RealmObject) realmModel ).isValid();
+ }
+ else {
+
+ try {
+ doReturn( flag ).when( RealmObject.class, "isValid", realmModel );
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * mark either realmModel or realmObject as loaded or not
+ * @param realmModel
+ * @param flag
+ */
+ public static void setLoaded(RealmModel realmModel, Boolean flag ){
+
+ if( realmModel instanceof RealmObject ){
+ doReturn( flag ).when( ((RealmObject) realmModel) ).isLoaded();
+ }
+ else {
+
+ try {
+ doReturn( flag ).when( RealmObject.class, "isLoaded", realmModel );
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static void deleteRealmModel( RealmModel realmModel ){
+ if( realmModel instanceof RealmObject){
+ ((RealmObject) realmModel ).deleteFromRealm();
+ }
+ else{
+ RealmObject.deleteFromRealm( realmModel );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/decorators/RealmObjectDecorator.java b/src/main/java/info/juanmendez/mockrealm/decorators/RealmObjectDecorator.java
new file mode 100644
index 0000000..66123df
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/decorators/RealmObjectDecorator.java
@@ -0,0 +1,267 @@
+package info.juanmendez.mockrealm.decorators;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.reflect.Whitebox;
+
+import java.lang.reflect.Field;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import info.juanmendez.mockrealm.dependencies.RealmObservable;
+import info.juanmendez.mockrealm.dependencies.RealmStorage;
+import info.juanmendez.mockrealm.dependencies.TransactionObservable;
+import info.juanmendez.mockrealm.models.TransactionEvent;
+import info.juanmendez.mockrealm.utils.QueryTracker;
+import info.juanmendez.mockrealm.utils.RealmModelUtil;
+import info.juanmendez.mockrealm.utils.SubscriptionsUtil;
+import io.realm.RealmChangeListener;
+import io.realm.RealmList;
+import io.realm.RealmModel;
+import io.realm.RealmObject;
+import io.realm.RealmResults;
+import io.realm.exceptions.RealmException;
+import rx.Observable;
+import rx.subjects.BehaviorSubject;
+
+import static org.mockito.Matchers.any;
+import static org.powermock.api.mockito.PowerMockito.doAnswer;
+
+/**
+ * Created by Juan Mendez on 3/19/2017.
+ * www.juanmendez.info
+ * contact@juanmendez.info
+ */
+
+public class RealmObjectDecorator {
+
+ private static SubscriptionsUtil subscriptionsUtil = new SubscriptionsUtil<>();
+
+ public static void prepare(){
+ handleClassActions();
+ }
+
+ static void handleClassActions(){
+
+ try {
+
+ doAnswer(new Answer() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+
+ RealmModel realmModel = (RealmModel) invocation.getArguments()[0];
+ RealmStorage.removeModel( realmModel );
+ return null;
+ }
+ }).when( RealmObject.class, "deleteFromRealm", any( RealmModel.class ) );
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ static void handleDeleteActions(RealmObject realmObject){
+
+ //when deleting then also make all subscriptions be unsubscribed
+ doAnswer(new Answer() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ RealmObservable.unsubcribe( realmObject );
+
+ Set fieldSet = Whitebox.getAllInstanceFields(realmObject);
+
+ for (Field field: fieldSet) {
+
+ if( field.getType() == RealmList.class ){
+
+ RealmList list = (RealmList) Whitebox.getInternalState(realmObject, field.getName());
+
+ if( list != null )
+ list.clear();
+ }
+ }
+
+ RealmObservable.unsubcribe( realmObject );
+ RealmStorage.removeModel( realmObject );
+
+ //after deleting item, lets make it invalid
+ RealmModelDecorator.setValid( realmObject, false );
+ RealmModelDecorator.setLoaded( realmObject, false );
+ return null;
+ }
+ }).when( (RealmObject) realmObject ).deleteFromRealm();
+ }
+
+
+ private void deleteModel( RealmModel realmModel ){
+
+ RealmObservable.unsubcribe( realmModel );
+
+ Set fieldSet = Whitebox.getAllInstanceFields(realmModel);
+
+ for (Field field: fieldSet) {
+
+ if( field.getType() == RealmList.class ){
+
+ RealmList list = (RealmList) Whitebox.getInternalState(realmModel, field.getName());
+
+ if( list != null )
+ list.clear();
+ }
+ }
+
+ RealmObservable.unsubcribe( realmModel );
+ RealmStorage.removeModel( realmModel );
+
+ //after deleting item, lets make it invalid
+ RealmModelDecorator.setValid( realmModel, false );
+ RealmModelDecorator.setLoaded( realmModel, false );
+ }
+
+ /**
+ * avoid doing anything in case realmObject is part of a realmResult
+ * @param realmObject
+ */
+ public static void handleAsyncMethods( RealmObject realmObject ){
+
+ doAnswer(invocation -> {throw new RealmException("Synchronous realmModel cannot be reached with a changeListener");}).when( realmObject ).addChangeListener( any(RealmChangeListener.class));
+ doAnswer(invocation -> null).when( realmObject).removeChangeListener(any(RealmChangeListener.class));
+ doAnswer(invocation -> null).when( realmObject).removeChangeListeners();
+ doAnswer( invocation ->{throw new RealmException("Synchronous realmModel cannot be reached with a changeListener");} ).when( realmObject ).asObservable();
+ }
+
+ public static void handleAsyncMethods(RealmObject realmObject, QueryTracker queryTracker ){
+
+ doAnswer(invocation -> {
+
+ //execute query once associated
+ RealmChangeListener listener = (RealmChangeListener) invocation.getArguments()[0];
+ Observable.fromCallable(new Callable>() {
+ @Override
+ public RealmResults call() throws Exception {
+
+ return queryTracker.rewind();
+ }
+ }).subscribeOn(RealmDecorator.getTransactionScheduler())
+ .observeOn( RealmDecorator.getResponseScheduler() )
+ .subscribe(realmResults -> {
+
+ if( !realmResults.isEmpty())
+ listener.onChange( realmResults.get(0) );
+ else
+ listener.onChange( null );
+
+ });
+
+ //whenever there is a transaction ending, we compare previous result with current one.
+ //we transform both results as json objects and just do a check if strings are not the same
+ subscriptionsUtil.add( realmObject,
+ listener,
+ TransactionObservable.asObservable()
+ .subscribe( transactionEvent -> {
+
+ if( transactionEvent.getState() == TransactionEvent.END_TRANSACTION ){
+
+ String initialJson = "", currrentJson = "";
+
+ RealmResults realmResults = queryTracker.getRealmResults();
+
+ if( !realmResults.isEmpty() ){
+ initialJson = RealmModelUtil.getState( realmResults.get(0) );
+ }
+
+ realmResults = queryTracker.rewind();
+
+ if( !realmResults.isEmpty() ){
+ currrentJson = RealmModelUtil.getState( realmResults.get(0) );
+ }
+
+ if( !initialJson.equals( currrentJson )){
+
+ if( !realmResults.isEmpty() )
+ listener.onChange( realmResults.get(0) );
+ else
+ listener.onChange( null );
+ }
+ }
+ })
+ );
+
+ return null;
+ }).when( realmObject ).addChangeListener(any(RealmChangeListener.class));
+
+
+ doAnswer(invocation -> {
+ RealmChangeListener listener = (RealmChangeListener) invocation.getArguments()[0];
+ subscriptionsUtil.remove(listener);
+ return null;
+ }).when( realmObject ).removeChangeListener( any(RealmChangeListener.class));
+
+
+ doAnswer(invocation -> {
+ subscriptionsUtil.removeAll( realmObject );
+ return null;
+ }).when( realmObject ).removeChangeListeners();
+
+
+ doAnswer(invocation -> {
+ BehaviorSubject subject = BehaviorSubject.create();
+
+ subject.subscribeOn(RealmDecorator.getTransactionScheduler())
+ .observeOn( RealmDecorator.getResponseScheduler() );
+
+ //first time make a call!
+ Observable.fromCallable(new Callable() {
+ @Override
+ public RealmModel call() throws Exception {
+
+ RealmResults realmResults = queryTracker.rewind();
+
+ if( !realmResults.isEmpty())
+ return realmResults.get(0);
+ else
+ return realmObject;
+ }
+ }).subscribeOn(RealmDecorator.getTransactionScheduler())
+ .observeOn( RealmDecorator.getResponseScheduler() )
+ .subscribe(realmModel -> {
+ subject.onNext( realmModel);
+ });
+
+ TransactionObservable.asObservable()
+ .subscribe(transactionEvent -> {
+
+ if( transactionEvent.getState() == TransactionEvent.END_TRANSACTION ){
+ String initialJson = "", currrentJson = "";
+
+ RealmResults realmResults = queryTracker.getRealmResults();
+
+ if( !realmResults.isEmpty() ){
+ initialJson = RealmModelUtil.getState( realmResults.get(0) );
+ }
+
+ realmResults = queryTracker.rewind();
+
+ if( !realmResults.isEmpty() ){
+ currrentJson = RealmModelUtil.getState( realmResults.get(0) );
+ }
+
+ if( !initialJson.equals( currrentJson )){
+
+ if( !realmResults.isEmpty() )
+ subject.onNext( realmResults.get(0) );
+ else
+ subject.onNext( realmObject );
+ }
+ }
+ });
+
+ return subject.asObservable();
+ }).when( realmObject ).asObservable();
+
+ }
+
+ public static void removeSubscriptions(){
+ subscriptionsUtil.removeAll();
+ }
+}
diff --git a/src/main/java/info/juanmendez/mockrealm/decorators/RealmQueryDecorator.java b/src/main/java/info/juanmendez/mockrealm/decorators/RealmQueryDecorator.java
new file mode 100644
index 0000000..05d31b3
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/decorators/RealmQueryDecorator.java
@@ -0,0 +1,509 @@
+package info.juanmendez.mockrealm.decorators;
+
+import org.mockito.internal.util.reflection.Whitebox;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+import info.juanmendez.mockrealm.dependencies.Compare;
+import info.juanmendez.mockrealm.models.Query;
+import info.juanmendez.mockrealm.utils.QuerySort;
+import info.juanmendez.mockrealm.utils.QueryTracker;
+import io.realm.Case;
+import io.realm.RealmModel;
+import io.realm.RealmObject;
+import io.realm.RealmQuery;
+import io.realm.RealmResults;
+import io.realm.Sort;
+import io.realm.exceptions.RealmException;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyByte;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.anyFloat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyShort;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.anyVararg;
+import static org.powermock.api.mockito.PowerMockito.doAnswer;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+/**
+ * Created by @juanmendezinfo on 2/15/2017.
+ */
+public class RealmQueryDecorator {
+
+ //collections queried keyed by immediate class
+
+ public static RealmQuery create( QueryTracker queryTracker){
+
+ RealmQuery realmQuery = queryTracker.getRealmQuery();
+
+ when( realmQuery.toString() ).thenReturn( "Realm:" + queryTracker.getClazz() );
+ Whitebox.setInternalState( realmQuery, "clazz", queryTracker.getClazz());
+
+ handleCollectionMethods(queryTracker);
+ handleGroupingQueries(queryTracker);
+ handleMathMethods(queryTracker);
+ handleSearchMethods(queryTracker);
+ handleSortingMethods(queryTracker);
+ handleDistinct( queryTracker );
+
+ return realmQuery;
+ }
+
+ private static void handleCollectionMethods(QueryTracker queryTracker) {
+ RealmQuery realmQuery = queryTracker.getRealmQuery();
+
+ when( realmQuery.findAll() ).thenAnswer(invocation ->{
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ return queryTracker.rewind();
+ });
+
+ when( realmQuery.findAllAsync() ).thenAnswer(invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ return queryTracker.getRealmResults();
+ });
+
+ when( realmQuery.findFirst()).thenAnswer(invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ RealmResults realmResults = queryTracker.rewind();
+
+ if( realmResults.isEmpty() )
+ return null;
+ else
+ return realmResults.get(0);
+ });
+
+ when( realmQuery.findFirstAsync() ).thenAnswer(invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ RealmObject realmObject = (RealmObject) RealmModelDecorator.create( queryTracker.getClazz(), false );
+ RealmObjectDecorator.handleAsyncMethods( realmObject, queryTracker );
+ return realmObject;
+ });
+ }
+
+ private static void handleGroupingQueries(QueryTracker queryTracker) {
+
+ RealmQuery realmQuery = queryTracker.getRealmQuery();
+ when( realmQuery.or()).then( invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.or));
+ return realmQuery;
+ });
+
+ when( realmQuery.beginGroup()).then( invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.startGroup));
+ return realmQuery;
+ });
+
+
+ when( realmQuery.endGroup()).then( invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endGroup));
+ return realmQuery;
+ });
+
+
+ when( realmQuery.not() ).thenAnswer( invocation -> {
+ queryTracker.appendQuery(Query.build().setCondition(Compare.not));
+ return realmQuery;
+ });
+ }
+
+ private static void handleMathMethods( QueryTracker queryTracker){
+
+ RealmQuery realmQuery = queryTracker.getRealmQuery();
+
+ when( realmQuery.count() ).thenAnswer(new Answer() {
+ @Override
+ public Integer answer(InvocationOnMock invocation) throws Throwable {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ RealmResults realmResults = queryTracker.rewind();
+ return realmResults.size();
+ }
+ });
+
+ when( realmQuery.sum( anyString()) ).thenAnswer(new Answer() {
+ @Override
+ public Number answer(InvocationOnMock invocation) throws Throwable {
+
+ if( invocation.getArguments().length >= 1 ){
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ RealmResults realmResults = queryTracker.rewind();
+
+ String fieldName = (String) invocation.getArguments()[0];
+ return realmResults.sum( fieldName );
+ }
+
+ return null;
+ }
+ });
+
+ when( realmQuery.average(anyString()) ).thenAnswer(new Answer() {
+ @Override
+ public Number answer(InvocationOnMock invocation) throws Throwable {
+ if( invocation.getArguments().length >= 1 ){
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ RealmResults realmResults = queryTracker.rewind();
+
+ String fieldName = (String) invocation.getArguments()[0];
+ return realmResults.average(fieldName);
+ }
+
+ return null;
+ }
+ });
+
+
+ when( realmQuery.max(anyString()) ).thenAnswer(new Answer() {
+ @Override
+ public Number answer(InvocationOnMock invocation) throws Throwable {
+ if( invocation.getArguments().length >= 1 ){
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ RealmResults realmResults = queryTracker.rewind();
+
+ String fieldName = (String) invocation.getArguments()[0];
+ return realmResults.max(fieldName);
+ }
+ return null;
+ }
+ });
+
+ when( realmQuery.min(anyString()) ).thenAnswer(new Answer() {
+ @Override
+ public Number answer(InvocationOnMock invocation) throws Throwable {
+ if( invocation.getArguments().length >= 1 ){
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ RealmResults realmResults = queryTracker.rewind();
+
+ String fieldName = (String) invocation.getArguments()[0];
+ return realmResults.min(fieldName);
+ }
+ return null;
+ }
+ });
+
+ when( realmQuery.isNull(anyString())).thenAnswer( invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.isNull).setArgs(invocation.getArguments()));
+ return realmQuery;
+ });
+
+ when( realmQuery.isNotNull(anyString())).thenAnswer(invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.not));
+ queryTracker.appendQuery( Query.build().setCondition(Compare.isNull).setArgs(invocation.getArguments()));
+ return realmQuery;
+ });
+ }
+
+ private static void handleSearchMethods( QueryTracker queryTracker){
+ RealmQuery realmQuery = queryTracker.getRealmQuery();
+
+ when( realmQuery.lessThan( any(), anyInt() ) ).thenAnswer( createComparison(queryTracker, Compare.less ) );
+ when( realmQuery.lessThan( anyString(), anyByte()) ).thenAnswer( createComparison(queryTracker, Compare.less ) );
+ when( realmQuery.lessThan( anyString(), anyDouble() ) ).thenAnswer( createComparison(queryTracker, Compare.less ) );
+ when( realmQuery.lessThan( anyString(), anyFloat() ) ).thenAnswer( createComparison(queryTracker, Compare.less ) );
+ when( realmQuery.lessThan( anyString(), anyLong() ) ).thenAnswer( createComparison(queryTracker, Compare.less ) );
+ when( realmQuery.lessThan( anyString(), anyShort() ) ).thenAnswer( createComparison(queryTracker, Compare.less ) );
+ when( realmQuery.lessThan( anyString(), any(Date.class) ) ).thenAnswer( createComparison(queryTracker, Compare.less ) );
+
+ when( realmQuery.lessThanOrEqualTo( any(), anyInt() ) ).thenAnswer( createComparison(queryTracker, Compare.lessOrEqual ) );
+ when( realmQuery.lessThanOrEqualTo( anyString(), anyByte()) ).thenAnswer( createComparison(queryTracker, Compare.lessOrEqual) );
+ when( realmQuery.lessThanOrEqualTo( anyString(), anyDouble() ) ).thenAnswer( createComparison(queryTracker, Compare.lessOrEqual) );
+ when( realmQuery.lessThanOrEqualTo( anyString(), anyFloat() ) ).thenAnswer( createComparison(queryTracker, Compare.lessOrEqual) );
+ when( realmQuery.lessThanOrEqualTo( anyString(), anyLong() ) ).thenAnswer( createComparison(queryTracker, Compare.lessOrEqual) );
+ when( realmQuery.lessThanOrEqualTo( anyString(), anyShort() ) ).thenAnswer( createComparison(queryTracker, Compare.lessOrEqual) );
+ when( realmQuery.lessThanOrEqualTo( anyString(), any(Date.class) ) ).thenAnswer( createComparison(queryTracker, Compare.lessOrEqual) );
+
+ when( realmQuery.greaterThan( any(), anyInt() ) ).thenAnswer( createComparison(queryTracker, Compare.more) );
+ when( realmQuery.greaterThan( anyString(), anyByte()) ).thenAnswer( createComparison(queryTracker, Compare.more) );
+ when( realmQuery.greaterThan( anyString(), anyDouble() ) ).thenAnswer( createComparison(queryTracker, Compare.more) );
+ when( realmQuery.greaterThan( anyString(), anyFloat() ) ).thenAnswer( createComparison(queryTracker, Compare.more) );
+ when( realmQuery.greaterThan( anyString(), anyLong() ) ).thenAnswer( createComparison(queryTracker, Compare.more) );
+ when( realmQuery.greaterThan( anyString(), anyShort() ) ).thenAnswer( createComparison(queryTracker, Compare.more) );
+ when( realmQuery.greaterThan( anyString(), any(Date.class) ) ).thenAnswer( createComparison(queryTracker, Compare.more) );
+
+ when( realmQuery.greaterThanOrEqualTo( any(), anyInt() ) ).thenAnswer( createComparison(queryTracker, Compare.moreOrEqual) );
+ when( realmQuery.greaterThanOrEqualTo( anyString(), anyByte()) ).thenAnswer( createComparison(queryTracker, Compare.moreOrEqual) );
+ when( realmQuery.greaterThanOrEqualTo( anyString(), anyDouble() ) ).thenAnswer( createComparison(queryTracker, Compare.moreOrEqual) );
+ when( realmQuery.greaterThanOrEqualTo( anyString(), anyFloat() ) ).thenAnswer( createComparison(queryTracker, Compare.moreOrEqual) );
+ when( realmQuery.greaterThanOrEqualTo( anyString(), anyLong() ) ).thenAnswer( createComparison(queryTracker, Compare.moreOrEqual) );
+ when( realmQuery.greaterThanOrEqualTo( anyString(), anyShort() ) ).thenAnswer( createComparison(queryTracker, Compare.moreOrEqual) );
+ when( realmQuery.greaterThanOrEqualTo( anyString(), any(Date.class) ) ).thenAnswer( createComparison(queryTracker, Compare.moreOrEqual) );
+
+ when( realmQuery.between( anyString(), anyInt(), anyInt() ) ).thenAnswer( createComparison(queryTracker, Compare.between) );
+ when( realmQuery.between( anyString(), any(Date.class), any(Date.class) ) ).thenAnswer( createComparison(queryTracker, Compare.between) );
+ when( realmQuery.between( anyString(), anyDouble(), anyDouble() ) ).thenAnswer( createComparison(queryTracker, Compare.between) );
+ when( realmQuery.between( anyString(), anyFloat(), anyFloat() ) ).thenAnswer( createComparison(queryTracker, Compare.between) );
+ when( realmQuery.between( anyString(), anyLong(), anyLong() ) ).thenAnswer( createComparison(queryTracker, Compare.between) );
+ when( realmQuery.between( anyString(), anyShort(), anyShort() ) ).thenAnswer( createComparison(queryTracker, Compare.between) );
+
+
+ when( realmQuery.equalTo( anyString(), anyInt() ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), anyByte()) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), anyDouble() ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), anyFloat() ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), anyLong() ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), anyString() ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), anyString(), any(Case.class) ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), anyBoolean() ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), anyShort() ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+ when( realmQuery.equalTo( anyString(), any(Date.class) ) ).thenAnswer( createComparison(queryTracker, Compare.equal ) );
+
+
+ when( realmQuery.notEqualTo( anyString(), anyInt() ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), anyByte()) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), anyDouble() ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), anyFloat() ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), anyLong() ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), anyString() ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), anyString(), any(Case.class) ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), anyBoolean() ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), anyShort() ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+ when( realmQuery.notEqualTo( anyString(), any(Date.class) ) ).thenAnswer( createComparison(queryTracker, Compare.equal, false ) );
+
+ when( realmQuery.contains( anyString(), anyString() ) ).thenAnswer( createComparison(queryTracker, Compare.contains ) );
+ when( realmQuery.contains( anyString(), anyString(), any(Case.class) ) ).thenAnswer( createComparison(queryTracker, Compare.contains ) );
+ when( realmQuery.endsWith( anyString(), anyString() ) ).thenAnswer( createComparison(queryTracker, Compare.endsWith ) );
+ when( realmQuery.endsWith( anyString(), anyString(), any(Case.class) ) ).thenAnswer( createComparison(queryTracker, Compare.endsWith ) );
+
+
+ when( realmQuery.in( anyString(), any(Integer[].class))).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(Byte[].class)) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(Double[].class) ) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(Float[].class) ) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(Long[].class) ) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(String[].class) ) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(String[].class), any(Case.class) ) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(Boolean[].class) ) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(Short[].class) ) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+ when( realmQuery.in( anyString(), any(Date[].class) ) ).thenAnswer( createComparison(queryTracker, Compare.in ) );
+
+ when( realmQuery.isEmpty(anyString())).thenAnswer(createComparison(queryTracker, Compare.isEmpty));
+ when( realmQuery.isNotEmpty(anyString())).thenAnswer(createComparison(queryTracker, Compare.isEmpty, false));
+ }
+
+ private static void handleSortingMethods( QueryTracker queryTracker ){
+ RealmQuery realmQuery = queryTracker.getRealmQuery();
+
+ doAnswer(invocation -> {
+ String field = (String) invocation.getArguments()[0];
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+ queryTracker.appendQuery( Query.build()
+ .setCondition(Compare.sort)
+ .setArgs(new Object[]{new QuerySort.SortField(field, true)}) );
+
+ return queryTracker.rewind();
+ }).when( realmQuery ).findAllSorted( anyString() );
+
+
+ doAnswer(invocation -> {
+ String field = (String) invocation.getArguments()[0];
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+
+
+ queryTracker.appendQuery( Query.build()
+ .setCondition(Compare.sort)
+ .setArgs(new Object[]{new QuerySort.SortField(field, true)}));
+
+ return queryTracker.getRealmResults();
+ }).when( realmQuery ).findAllSortedAsync( anyString() );
+
+
+ doAnswer(invocation -> {
+ String field = (String) invocation.getArguments()[0];
+ Sort sort = (Sort) invocation.getArguments()[1];
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ queryTracker.appendQuery( Query.build().setCondition(Compare.sort).setArgs(new Object[]{new QuerySort.SortField(field, sort.getValue())}));
+ return queryTracker.rewind();
+ }).when( realmQuery ).findAllSorted( anyString(), any(Sort.class));
+
+ doAnswer(invocation -> {
+ String field = (String) invocation.getArguments()[0];
+ Sort sort = (Sort) invocation.getArguments()[1];
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+ queryTracker.appendQuery( Query.build().setCondition(Compare.sort).setArgs(new Object[]{new QuerySort.SortField(field, sort.getValue())}));
+ return queryTracker.getRealmResults();
+ }).when( realmQuery ).findAllSortedAsync( anyString(), any(Sort.class));
+
+ doAnswer(invocation -> {
+
+ ArrayList sortFields = new ArrayList();
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+ //sorting goes in reverse order!
+ sortFields.add( new QuerySort.SortField((String) invocation.getArguments()[2], ((Sort) invocation.getArguments()[3]).getValue() ));
+ sortFields.add( new QuerySort.SortField((String) invocation.getArguments()[0], ((Sort) invocation.getArguments()[1]).getValue() ));
+
+ for (QuerySort.SortField sortField:sortFields) {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.sort).setArgs(new Object[]{sortField}));
+ }
+
+ return queryTracker.rewind();
+ }).when( realmQuery ).findAllSorted( anyString(), any(Sort.class), anyString(), any(Sort.class));
+
+
+ doAnswer(invocation -> {
+
+ ArrayList sortFields = new ArrayList();
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+ //sorting goes in reverse order!
+ sortFields.add( new QuerySort.SortField((String) invocation.getArguments()[2], ((Sort) invocation.getArguments()[3]).getValue() ));
+ sortFields.add( new QuerySort.SortField((String) invocation.getArguments()[0], ((Sort) invocation.getArguments()[1]).getValue() ));
+
+ for (QuerySort.SortField sortField:sortFields) {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.sort).setArgs(new Object[]{sortField}));
+ }
+
+ return queryTracker.getRealmResults();
+ }).when( realmQuery ).findAllSortedAsync( anyString(), any(Sort.class), anyString(), any(Sort.class));
+
+
+ doAnswer(invocation -> {
+
+ String[] fields = (String[])invocation.getArguments()[0];
+ Sort[] sorts = (Sort[])invocation.getArguments()[1];
+
+ if( fields.length != sorts.length ){
+ throw new RealmException("#mocking-realm: fields and sort arrays don't match" );
+ }
+
+ QuerySort.SortField sortField;
+ ArrayList sortFields = new ArrayList();
+ int top = fields.length-1;
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+ //sorting goes in reverse order!
+ for( int i = 0; i <= top; i++ ){
+
+ sortField = new QuerySort.SortField( fields[top-i], sorts[top-i].getValue() );
+ queryTracker.appendQuery( Query.build().setCondition(Compare.sort).setArgs(new Object[]{sortField}));
+ }
+
+ return queryTracker.rewind();
+ }).when( realmQuery ).findAllSorted( any(String[].class), any(Sort[].class));
+
+
+ doAnswer(invocation -> {
+
+ String[] fields = (String[])invocation.getArguments()[0];
+ Sort[] sorts = (Sort[])invocation.getArguments()[1];
+
+ if( fields.length != sorts.length ){
+ throw new RealmException("#mocking-realm: fields and sort arrays don't match" );
+ }
+
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+ QuerySort.SortField sortField;
+ ArrayList sortFields = new ArrayList();
+ int top = fields.length-1;
+
+ //sorting goes in reverse order!
+ for( int i = 0; i <= top; i++ ){
+
+ sortField = new QuerySort.SortField( fields[top-i], sorts[top-i].getValue() );
+
+ queryTracker.appendQuery( Query.build()
+ .setCondition(Compare.sort)
+ .setArgs(new Object[]{sortField}));
+ }
+
+ return queryTracker.getRealmResults();
+ }).when( realmQuery ).findAllSortedAsync( any(String[].class), any(Sort[].class));
+ }
+
+ private static void handleDistinct( QueryTracker queryTracker ){
+
+ RealmQuery realmQuery = queryTracker.getRealmQuery();
+
+ doAnswer(invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+ queryTracker.appendQuery( Query.build()
+ .setCondition(Compare.distinct)
+ .setArgs(invocation.getArguments()));
+
+ return queryTracker.rewind();
+ }).when(realmQuery).distinct(anyString());
+
+ doAnswer(invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+ for( Object arg: invocation.getArguments() ){
+ queryTracker.appendQuery( Query.build()
+ .setCondition(Compare.distinct)
+ .setArgs(new Object[]{arg}));
+ }
+ return queryTracker.rewind();
+ }).when(realmQuery).distinct(anyString(), anyVararg() );
+
+ doAnswer(invocation -> {
+ queryTracker.appendQuery( Query.build().setCondition(Compare.endTopGroup));
+
+
+
+ queryTracker.appendQuery( Query.build()
+ .setCondition(Compare.distinct)
+ .setArgs(invocation.getArguments()));
+
+ return queryTracker.getRealmResults();
+ }).when(realmQuery).distinctAsync(anyString());
+ }
+
+
+ /**
+ * This method filters using args.condition and updates queryMap
+ * @param queryTracker queryMap.get( realmQuery.clazz ) gives key to get collection from queryMap
+ * @param condition based on Compare.enums
+ * @return the same args.realmQuery
+ */
+
+ public static Answer createComparison(QueryTracker queryTracker, String condition ){
+ return createComparison(queryTracker, condition, true );
+ };
+
+ public static Answer createComparison(QueryTracker queryTracker, String condition, Boolean assertive ){
+
+ RealmQuery realmQuery = queryTracker.getRealmQuery();
+
+ return invocationOnMock -> {
+
+ int argsLen = invocationOnMock.getArguments().length;
+ String type = "";
+
+ if( argsLen >= 1 ){
+ type = (String) invocationOnMock.getArguments()[0];
+
+ if( type.isEmpty() )
+ return realmQuery;
+ }
+ else if( argsLen < 2 ){
+ return realmQuery;
+ }
+
+ if( !assertive ){
+ queryTracker.appendQuery(Query.build().setCondition(Compare.not));
+ }
+
+ queryTracker.appendQuery(Query.build()
+ .setCondition(condition)
+ .setField(type)
+ .setArgs(invocationOnMock.getArguments()));
+
+ return realmQuery;
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/decorators/RealmResultsDecorator.java b/src/main/java/info/juanmendez/mockrealm/decorators/RealmResultsDecorator.java
new file mode 100644
index 0000000..b792774
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/decorators/RealmResultsDecorator.java
@@ -0,0 +1,390 @@
+package info.juanmendez.mockrealm.decorators;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+
+import info.juanmendez.mockrealm.dependencies.Compare;
+import info.juanmendez.mockrealm.dependencies.TransactionObservable;
+import info.juanmendez.mockrealm.models.Query;
+import info.juanmendez.mockrealm.models.TransactionEvent;
+import info.juanmendez.mockrealm.utils.QuerySort;
+import info.juanmendez.mockrealm.utils.QueryTracker;
+import info.juanmendez.mockrealm.utils.RealmModelUtil;
+import info.juanmendez.mockrealm.utils.SubscriptionsUtil;
+import io.realm.RealmChangeListener;
+import io.realm.RealmList;
+import io.realm.RealmModel;
+import io.realm.RealmObject;
+import io.realm.RealmQuery;
+import io.realm.RealmResults;
+import io.realm.Sort;
+import io.realm.exceptions.RealmException;
+import rx.Observable;
+import rx.subjects.BehaviorSubject;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.powermock.api.mockito.PowerMockito.doAnswer;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+/**
+ * Created by @juanmendezinfo on 2/15/2017.
+ *
+ * RealmResults methods rely on RealmListStubbed, which is a subclass of RealmList.
+ */
+public class RealmResultsDecorator {
+
+ private static SubscriptionsUtil subscriptionsUtil = new SubscriptionsUtil<>();
+
+ public static RealmResults create(QueryTracker queryTracker ){
+
+ RealmResults realmResults = queryTracker.getRealmResults();
+ RealmList realmList = queryTracker.getQueryList();
+
+ doAnswer(new Answer() {
+
+ @Override
+ public RealmQuery answer(InvocationOnMock invocationOnMock) throws Throwable {
+
+ QueryTracker resultsQueryTracker = queryTracker.clone();
+ resultsQueryTracker.appendQuery( Query.build().setCondition(Compare.startTopGroup).setArgs(new Object[]{realmList}));
+
+ RealmQuery realmQuery = RealmQueryDecorator.create(resultsQueryTracker);
+
+ return realmQuery;
+ }
+ }).when(realmResults).where();
+
+ /**
+ * realmList is a shadow of realmResults.
+ */
+ handleBasicActions( realmResults, realmList );
+ handleDeleteMethods( realmResults, realmList );
+ handleMathMethods( realmResults, realmList );
+ handleAsyncMethods( queryTracker );
+ handleSorting( queryTracker );
+
+ return realmResults;
+ }
+
+ private static void handleBasicActions( RealmResults realmResults, RealmList list){
+
+ doAnswer(positionInvokation -> {
+ int position = (int) positionInvokation.getArguments()[0];
+ return list.get( position );
+ }).when( realmResults).get(anyInt());
+
+ doAnswer(invocation -> {
+ return list.size();
+ }).when( realmResults ).size();
+
+ doAnswer(invocation -> {
+ return list.isEmpty();
+ }).when( realmResults ).isEmpty();
+
+ doAnswer(invocation -> {
+ return list.iterator();
+ }).when( realmResults ).iterator();
+
+
+ doAnswer(new Answer() {
+ @Override
+ public RealmObject answer(InvocationOnMock invocationOnMock) throws Throwable {
+ int index = (int) invocationOnMock.getArguments()[0];
+ RealmObject value = (RealmObject) invocationOnMock.getArguments()[0];
+ list.set(index, value);
+ return value;
+ }
+ }).when( realmResults ).set(anyInt(), any(RealmObject.class) );
+
+ doAnswer(new Answer() {
+ @Override
+ public RealmModel answer(InvocationOnMock invocationOnMock) throws Throwable {
+ int index = (int) invocationOnMock.getArguments()[0];
+ return list.get(index);
+ }
+ }).when( realmResults ).listIterator(anyInt());
+
+ }
+
+ private static void handleDeleteMethods( RealmResults realmResults, RealmList list ){
+
+
+ doAnswer(new Answer() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ return list.deleteAllFromRealm();
+ }
+ }).when(realmResults).deleteAllFromRealm();
+
+
+ doAnswer(new Answer() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ return list.deleteFirstFromRealm();
+ }
+ }).when( realmResults ).deleteFirstFromRealm();
+
+
+ doAnswer(new Answer() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ return list.deleteLastFromRealm();
+ }
+ }).when( realmResults ).deleteLastFromRealm();
+
+
+ doAnswer( new Answer() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+
+ int position = (int) invocation.getArguments()[0];
+ list.deleteFromRealm( position );
+
+ return null;
+ }
+ }).when( realmResults ).deleteFromRealm( anyInt() );
+ }
+
+ /**
+ * The supported methods from realmResults are calling
+ * and returning values from the counterpart methods of list
+ * @param realmResults
+ * @param list
+ */
+ private static void handleMathMethods( RealmResults realmResults, RealmList list ){
+
+ //realmResults.sum( fieldString )
+ when( realmResults.sum( anyString()) ).thenAnswer(new Answer() {
+ @Override
+ public Number answer(InvocationOnMock invocation) throws Throwable {
+
+ if( invocation.getArguments().length >= 1 ){
+
+ String fieldName = (String) invocation.getArguments()[0];
+ return list.sum( fieldName );
+ }
+
+ return null;
+ }
+ });
+
+ //realmResults.average( fieldString )
+ when( realmResults.average(anyString()) ).thenAnswer(new Answer() {
+ @Override
+ public Number answer(InvocationOnMock invocation) throws Throwable {
+ if( invocation.getArguments().length >= 1 ){
+
+ String fieldName = (String) invocation.getArguments()[0];
+ return list.average(fieldName);
+ }
+
+ return null;
+ }
+ });
+
+ //realmResults.max( fieldString )
+ when( realmResults.max(anyString()) ).thenAnswer(new Answer() {
+ @Override
+ public Number answer(InvocationOnMock invocation) throws Throwable {
+ if( invocation.getArguments().length >= 1 ){
+
+ String fieldName = (String) invocation.getArguments()[0];
+ return list.max(fieldName);
+ }
+ return null;
+ }
+ });
+
+ //realmResults.min( fieldString )
+ when( realmResults.min(anyString()) ).thenAnswer(new Answer() {
+ @Override
+ public Number answer(InvocationOnMock invocation) throws Throwable {
+ if( invocation.getArguments().length >= 1 ){
+
+ String fieldName = (String) invocation.getArguments()[0];
+ return list.min(fieldName);
+ }
+ return null;
+ }
+ });
+ }
+
+ private static void handleAsyncMethods( QueryTracker queryTracker){
+
+ RealmResults realmResults = queryTracker.getRealmResults();
+
+ doAnswer(invocation -> {
+
+ //execute query once associated
+ RealmChangeListener listener = (RealmChangeListener) invocation.getArguments()[0];
+ Observable.fromCallable(() -> queryTracker.rewind())
+ .subscribeOn(RealmDecorator.getTransactionScheduler())
+ .observeOn( RealmDecorator.getResponseScheduler() )
+ .subscribe(results -> {
+ listener.onChange( results );
+ });
+
+
+ final String[] json = new String[2];
+
+ //whenever there is a transaction ending, we compare previous result with current one.
+ //we transform both results as json objects and just do a check if strings are not the same
+ subscriptionsUtil.add( realmResults,
+ listener,
+ TransactionObservable.asObservable()
+ .subscribe( transactionEvent -> {
+
+ if( transactionEvent.getState() == TransactionEvent.END_TRANSACTION ){
+
+ String initialJson = "", currrentJson = "";
+
+ RealmResults results = queryTracker.getRealmResults();
+ initialJson = RealmModelUtil.getState( results );
+
+ results = queryTracker.rewind();
+ currrentJson = RealmModelUtil.getState( results );
+
+ if( !initialJson.equals( currrentJson )){
+ listener.onChange( results );
+ }
+ }
+ })
+ );
+
+ return null;
+ }).when( realmResults ).addChangeListener(any(RealmChangeListener.class));
+
+
+ doAnswer(invocation -> {
+ RealmChangeListener listener = (RealmChangeListener) invocation.getArguments()[0];
+ subscriptionsUtil.remove(listener);
+ return null;
+ }).when( realmResults ).removeChangeListener( any(RealmChangeListener.class));
+
+
+ doAnswer(invocation -> {
+ subscriptionsUtil.removeAll( realmResults );
+ return null;
+ }).when( realmResults ).removeChangeListeners();
+
+
+ doAnswer(invocation -> {
+ BehaviorSubject subject = BehaviorSubject.create();
+
+ subject.subscribeOn(RealmDecorator.getTransactionScheduler())
+ .observeOn( RealmDecorator.getResponseScheduler() );
+
+ //first time make a call!
+ Observable.fromCallable(() -> queryTracker.rewind())
+ .subscribeOn(RealmDecorator.getTransactionScheduler())
+ .observeOn( RealmDecorator.getResponseScheduler() )
+ .subscribe(results -> {
+ subject.onNext( results );
+ });
+
+ TransactionObservable.asObservable()
+ .subscribe(transactionEvent -> {
+
+ if( transactionEvent.getState() == TransactionEvent.END_TRANSACTION ){
+ String initialJson = "", currrentJson = "";
+
+ RealmResults results = queryTracker.getRealmResults();
+
+ initialJson = RealmModelUtil.getState( results );
+
+ results = queryTracker.rewind();
+ currrentJson = RealmModelUtil.getState( results );
+
+ if( !initialJson.equals( currrentJson )){
+ subject.onNext( results );
+ }
+ }
+ });
+
+ return subject;
+ }).when( realmResults ).asObservable();
+ }
+
+
+ private static void handleSorting(QueryTracker queryTracker) {
+
+ RealmResults realmResults = queryTracker.getRealmResults();
+
+ doAnswer(invocation -> {
+ String field = (String) invocation.getArguments()[0];
+ ArrayList sortFields = new ArrayList();
+ sortFields.add( new QuerySort.SortField(field, true));
+ return invokeSort( queryTracker, sortFields );
+ }).when( realmResults ).sort( anyString() );
+
+
+ doAnswer(invocation -> {
+
+ String field = (String) invocation.getArguments()[0];
+ Sort sort = (Sort) invocation.getArguments()[1];
+
+ ArrayList sortFields = new ArrayList();
+ sortFields.add( new QuerySort.SortField(field, sort.getValue() ));
+
+ return invokeSort( queryTracker, sortFields );
+ }).when( realmResults ).sort( anyString(), any(Sort.class));
+
+
+ doAnswer(invocation -> {
+
+ ArrayList sortFields = new ArrayList();
+
+ //sorting goes in reverse order!
+ sortFields.add( new QuerySort.SortField((String) invocation.getArguments()[2], ((Sort) invocation.getArguments()[3]).getValue() ));
+ sortFields.add( new QuerySort.SortField((String) invocation.getArguments()[0], ((Sort) invocation.getArguments()[1]).getValue() ));
+
+ return invokeSort( queryTracker, sortFields );
+ }).when( realmResults ).sort( anyString(), any(Sort.class), anyString(), any(Sort.class));
+
+
+
+ doAnswer(invocation -> {
+
+ String[] fields = (String[])invocation.getArguments()[0];
+ Sort[] sorts = (Sort[])invocation.getArguments()[1];
+
+ if( fields.length != sorts.length ){
+ throw new RealmException("#mocking-realm: fields and sort arrays don't match" );
+ }
+
+ ArrayList sortFields = new ArrayList();
+ int top = fields.length-1;
+
+ //sorting goes in reverse order!
+ for( int i = 0; i <= top; i++ ){
+ sortFields.add( new QuerySort.SortField( fields[top-i], sorts[top-i].getValue() ) );
+ }
+
+ return invokeSort( queryTracker, sortFields );
+ }).when( realmResults ).sort( any(String[].class), any(Sort[].class));
+
+ //doAnswer(invocation -> { return realmResults; }).when( realmResults ).sort( any(Comparator.class));
+ }
+
+ private static RealmResults invokeSort(QueryTracker queryTracker, ArrayList sortFields ){
+
+ QueryTracker resultsQueryTracker = queryTracker.clone();
+ resultsQueryTracker.appendQuery(Query.build().setCondition(Compare.startTopGroup).setArgs(new Object[]{queryTracker.getQueryList()}));
+ resultsQueryTracker.appendQuery(Query.build().setCondition(Compare.endTopGroup));
+
+ for (QuerySort.SortField sortField: sortFields ) {
+
+ resultsQueryTracker.appendQuery(Query.build().setCondition(Compare.sort).setArgs(new Object[]{sortField}));
+ }
+
+ return resultsQueryTracker.rewind();
+ }
+
+ public static void removeSubscriptions(){
+ subscriptionsUtil.removeAll();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/dependencies/Compare.java b/src/main/java/info/juanmendez/mockrealm/dependencies/Compare.java
new file mode 100644
index 0000000..440c1cf
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/dependencies/Compare.java
@@ -0,0 +1,33 @@
+package info.juanmendez.mockrealm.dependencies;
+
+/**
+ * Created by @juanmendezinfo on 2/15/2017.
+ */
+public class Compare{
+ public static final String less = "less";
+ public static final String lessOrEqual = "lessOrEqual";
+ public static final String equal = "equal";
+ public static final String more = "more";
+ public static final String moreOrEqual = "moreOrEqual";
+ public static final String between = "between";
+
+ public static final String contains = "contains";
+ public static final String startWith = "starsWith";
+ public static final String endsWith = "endsWith";
+ public static final String in = "in";
+
+ public static final String or = "or";
+ public static final String not = "not";
+ public static final String startGroup = "startGroup";
+ public static final String endGroup = "endGroup";
+ public static final String startTopGroup = "startTopGroup";
+ public static final String endTopGroup = "endTopGroup";
+ public static final String isNull = "isNull";
+
+ public static final String findAll = "findAll";
+ public static final String findFirst = "findFirst";
+ public static final String distinct = "distinct";
+ public static final String sort = "sort";
+ public static final String async = "async";
+ public static final String isEmpty = "isEmpty";
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/dependencies/RealmMatchers.java b/src/main/java/info/juanmendez/mockrealm/dependencies/RealmMatchers.java
new file mode 100644
index 0000000..8ff515a
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/dependencies/RealmMatchers.java
@@ -0,0 +1,48 @@
+package info.juanmendez.mockrealm.dependencies;
+
+import org.mockito.ArgumentMatcher;
+
+/**
+ * Created by @juanmendezinfo on 2/15/2017.
+ */
+public class RealmMatchers {
+
+ /**
+ * argument matcher checks if targetClass is the super class of the object passed.
+ * @param
+ */
+ public static class ClassMatcher extends ArgumentMatcher> {
+
+ private final Class targetClass;
+
+ public ClassMatcher(Class targetClass) {
+ this.targetClass = targetClass;
+ }
+
+ public boolean matches(Object obj) {
+
+ if (obj != null && obj instanceof Class) {
+ return targetClass.isAssignableFrom((Class) obj);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * checks if instance matches class
+ * @param
+ */
+ class InstanceMatcher extends ArgumentMatcher{
+
+ private final Class targetClass;
+
+ public InstanceMatcher(Class targetClass) {
+ this.targetClass = targetClass;
+ }
+
+ @Override
+ public boolean matches(Object o) {
+ return targetClass.isInstance( o );
+ }
+ }
+}
diff --git a/src/main/java/info/juanmendez/mockrealm/dependencies/RealmObservable.java b/src/main/java/info/juanmendez/mockrealm/dependencies/RealmObservable.java
new file mode 100644
index 0000000..08429eb
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/dependencies/RealmObservable.java
@@ -0,0 +1,59 @@
+package info.juanmendez.mockrealm.dependencies;
+
+import info.juanmendez.mockrealm.models.RealmEvent;
+import info.juanmendez.mockrealm.utils.SubscriptionsUtil;
+import rx.Observable;
+import rx.Subscription;
+import rx.subjects.BehaviorSubject;
+
+/**
+ * Created by Juan Mendez on 3/10/2017.
+ * www.juanmendez.info
+ * contact@juanmendez.info
+ *
+ * Provides a subject which can be observed whenever realm is getting a realmModel added or removed
+ * Elements emitted are of type RealmEvent. RealmEvent wraps the state and realmModel
+ */
+
+public class RealmObservable {
+
+ private static BehaviorSubject realmModelObserver = BehaviorSubject.create();
+ private static TransactionObservable to = new TransactionObservable();
+ private static SubscriptionsUtil subscriptionsUtil = new SubscriptionsUtil();
+
+
+ public static Observable asObservable() {
+ return realmModelObserver.asObservable();
+ }
+
+ public static void onNext(RealmEvent realmModelState){
+ realmModelObserver.onNext( realmModelState);
+ }
+
+ public static void add(Subscription subscription ){
+ subscriptionsUtil.add(to, subscription );
+ }
+
+ public static void add( Object observer, Subscription subscription ){
+ subscriptionsUtil.add(to, observer, subscription );
+ }
+
+ public static void remove( Subscription subscription ){
+ subscriptionsUtil.remove(to, subscription );
+ }
+
+ public static void remove( Object observer, Subscription subscription ){
+ subscriptionsUtil.remove(observer, subscription);
+ }
+
+ public static void removeSubscriptions(){
+ subscriptionsUtil.removeAll();
+
+ //recreate subject when RealmStorage.clear()
+ realmModelObserver = BehaviorSubject.create();
+ }
+
+ public static void unsubcribe( Object observer ){
+ subscriptionsUtil.remove( observer );
+ }
+}
diff --git a/src/main/java/info/juanmendez/mockrealm/dependencies/RealmStorage.java b/src/main/java/info/juanmendez/mockrealm/dependencies/RealmStorage.java
new file mode 100644
index 0000000..ea6e6c1
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/dependencies/RealmStorage.java
@@ -0,0 +1,85 @@
+package info.juanmendez.mockrealm.dependencies;
+
+import java.util.HashMap;
+
+import info.juanmendez.mockrealm.decorators.RealmObjectDecorator;
+import info.juanmendez.mockrealm.decorators.RealmResultsDecorator;
+import info.juanmendez.mockrealm.models.RealmAnnotation;
+import info.juanmendez.mockrealm.models.RealmEvent;
+import info.juanmendez.mockrealm.utils.RealmModelUtil;
+import io.realm.RealmList;
+import io.realm.RealmModel;
+import io.realm.exceptions.RealmException;
+
+/**
+ * Created by @juanmendezinfo on 2/15/2017.
+ */
+public class RealmStorage {
+
+ private static HashMap> realmMap = new HashMap<>();
+ private static HashMap annotationMap = new HashMap<>();
+
+ /*keeps collections keyed by a sub-class of RealmModel.*/
+ public static HashMap> getRealmMap() {
+ return realmMap;
+ }
+
+ public static void removeModel( RealmModel realmModel ){
+
+ if( realmModel != null ){
+
+ Class clazz = RealmModelUtil.getClass(realmModel);
+
+ if( RealmModel.class.isAssignableFrom(clazz) ){
+
+ if( realmMap.get(clazz) != null && realmMap.get(clazz).contains( realmModel ) ){
+ realmMap.get( clazz ).remove( realmModel );
+ RealmObservable.onNext( new RealmEvent( RealmEvent.MODEL_REMOVED, realmModel ) );
+ }else{
+ throw new RealmException( "Instance of " + clazz.getName() + " cannot be deleted as it's not part of the realm database" );
+ }
+
+ }else{
+
+ throw new RealmException( clazz.getName() + " is not an instance of RealmModel" );
+ }
+ }
+ }
+
+ public static void addModel( RealmModel realmModel ){
+
+ if( realmModel != null ){
+
+ Class clazz = RealmModelUtil.getClass(realmModel);
+
+ if( RealmModel.class.isAssignableFrom(clazz) ){
+
+ if( !realmMap.get(clazz).contains( realmModel ) ){
+ realmMap.get(RealmModelUtil.getClass(realmModel) ).add( realmModel );
+ RealmObservable.onNext( new RealmEvent( RealmEvent.MODEL_ADDED, realmModel ) );
+ }else{
+ System.out.println( "#mocking-realm: Instance of " + clazz.getName() + " cannot be added more than once" );
+ }
+ }else{
+
+ throw new RealmException( clazz.getName() + " is not an instance of RealmModel" );
+ }
+ }
+ }
+
+ public static void clear(){
+ RealmObservable.removeSubscriptions();
+ TransactionObservable.removeSubscriptions();
+ RealmObjectDecorator.removeSubscriptions();
+ RealmResultsDecorator.removeSubscriptions();
+ realmMap.clear();
+ }
+
+ public static void addAnnotations( RealmAnnotation realmAnnotation ){
+ annotationMap.put( realmAnnotation.getClazz(), realmAnnotation );
+ }
+
+ public static HashMap getAnnotationMap() {
+ return annotationMap;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/info/juanmendez/mockrealm/dependencies/TransactionObservable.java b/src/main/java/info/juanmendez/mockrealm/dependencies/TransactionObservable.java
new file mode 100644
index 0000000..c2031e0
--- /dev/null
+++ b/src/main/java/info/juanmendez/mockrealm/dependencies/TransactionObservable.java
@@ -0,0 +1,132 @@
+package info.juanmendez.mockrealm.dependencies;
+
+import java.util.ArrayList;
+
+import info.juanmendez.mockrealm.models.TransactionEvent;
+import info.juanmendez.mockrealm.utils.SubscriptionsUtil;
+import rx.Observable;
+import rx.Subscription;
+import rx.subjects.PublishSubject;
+
+/**
+ * Created by Juan Mendez on 3/17/2017.
+ * www.juanmendez.info
+ * contact@juanmendez.info
+ *
+ */
+public class TransactionObservable {
+ private static TransactionObservable instance;
+ private static SubscriptionsUtil subscriptionsUtil = new SubscriptionsUtil();
+ private static PublishSubject subject = PublishSubject.create();
+
+ private static ArrayList