Skip to content

Commit

Permalink
Add StorIOContentResolver get().numberOfResults()!
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitin-da committed Sep 30, 2015
1 parent 4abd1ab commit 42b626d
Show file tree
Hide file tree
Showing 8 changed files with 457 additions and 14 deletions.
Expand Up @@ -3,6 +3,7 @@
import android.support.annotation.NonNull;

import com.pushtorefresh.storio.contentresolver.StorIOContentResolver;
import com.pushtorefresh.storio.contentresolver.queries.Query;
import com.pushtorefresh.storio.operations.PreparedOperation;

/**
Expand All @@ -15,8 +16,12 @@ public abstract class PreparedGet<Result> implements PreparedOperation<Result> {
@NonNull
protected final StorIOContentResolver storIOContentResolver;

PreparedGet(@NonNull StorIOContentResolver storIOContentResolver) {
@NonNull
protected final Query query;

PreparedGet(@NonNull StorIOContentResolver storIOContentResolver, @NonNull Query query) {
this.storIOContentResolver = storIOContentResolver;
this.query = query;
}

/**
Expand Down Expand Up @@ -52,5 +57,15 @@ public PreparedGetCursor.Builder cursor() {
public <T> PreparedGetListOfObjects.Builder<T> listOfObjects(@NonNull Class<T> type) {
return new PreparedGetListOfObjects.Builder<T>(storIOContentResolver, type);
}

/**
* Returns builder for Get Operation that returns number of results.
*
* @return builder for Get Operation that returns number of results.
*/
@NonNull
public PreparedGetNumberOfResults.Builder numberOfResults() {
return new PreparedGetNumberOfResults.Builder(storIOContentResolver);
}
}
}
Expand Up @@ -25,17 +25,13 @@
*/
public final class PreparedGetCursor extends PreparedGet<Cursor> {

@NonNull
private final Query query;

@NonNull
private final GetResolver<Cursor> getResolver;

PreparedGetCursor(@NonNull StorIOContentResolver storIOContentResolver,
@NonNull GetResolver<Cursor> getResolver,
@NonNull Query query) {
super(storIOContentResolver);
this.query = query;
super(storIOContentResolver, query);
this.getResolver = getResolver;
}

Expand Down
Expand Up @@ -36,19 +36,15 @@ public final class PreparedGetListOfObjects<T> extends PreparedGet<List<T>> {
@NonNull
private final Class<T> type;

@NonNull
private final Query query;

@Nullable
private final GetResolver<T> explicitGetResolver;

PreparedGetListOfObjects(@NonNull StorIOContentResolver storIOContentResolver,
@NonNull Class<T> type,
@NonNull Query query,
@Nullable GetResolver<T> explicitGetResolver) {
super(storIOContentResolver);
super(storIOContentResolver, query);
this.type = type;
this.query = query;
this.explicitGetResolver = explicitGetResolver;
}

Expand Down Expand Up @@ -140,7 +136,7 @@ public Observable<List<T>> createObservable() {
}

/**
* Compile-time safe part of builder for {@link PreparedGetListOfObjects}.
* Builder for {@link PreparedGetListOfObjects}.
*
* @param <T> type of objects for query.
*/
Expand Down
@@ -0,0 +1,175 @@
package com.pushtorefresh.storio.contentresolver.operations.get;

import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;

import com.pushtorefresh.storio.StorIOException;
import com.pushtorefresh.storio.contentresolver.StorIOContentResolver;
import com.pushtorefresh.storio.contentresolver.queries.Query;
import com.pushtorefresh.storio.operations.internal.MapSomethingToExecuteAsBlocking;
import com.pushtorefresh.storio.operations.internal.OnSubscribeExecuteAsBlocking;

import rx.Observable;
import rx.schedulers.Schedulers;

import static com.pushtorefresh.storio.internal.Checks.checkNotNull;
import static com.pushtorefresh.storio.internal.Environment.throwExceptionIfRxJavaIsNotAvailable;

public class PreparedGetNumberOfResults extends PreparedGet<Integer> {

@NonNull
private final GetResolver<Integer> getResolver;

PreparedGetNumberOfResults(@NonNull StorIOContentResolver storIOContentResolver, @NonNull Query query, @NonNull GetResolver<Integer> getResolver) {
super(storIOContentResolver, query);
this.getResolver = getResolver;
}

/**
* Executes Get Operation immediately in current thread.
* <p>
* Notice: This is blocking I/O operation that should not be executed on the Main Thread,
* it can cause ANR (Activity Not Responding dialog), block the UI and drop animations frames.
* So please, call this method on some background thread. See {@link WorkerThread}.
*
* @return non-null {@link Integer} with number of results of the query.
*/
@WorkerThread
@NonNull
@Override
public Integer executeAsBlocking() {
final Cursor cursor;

try {
cursor = getResolver.performGet(storIOContentResolver, query);
try {
return getResolver.mapFromCursor(cursor);
} finally {
cursor.close();
}
} catch (Exception exception) {
throw new StorIOException(exception);
}
}

/**
* Creates "Hot" {@link Observable} which will be subscribed to changes of tables from query
* and will emit result each time change occurs.
* <p>
* First result will be emitted immediately after subscription,
* other emissions will occur only if changes of tables from query will occur during lifetime of
* the {@link Observable}.
* <dl>
* <dt><b>Scheduler:</b></dt>
* <dd>Operates on {@link Schedulers#io()}.</dd>
* </dl>
* <p>
* Please don't forget to unsubscribe from this {@link Observable} because
* it's "Hot" and endless.
*
* @return non-null {@link Observable} which will emit non-null
* number of results of the executed query and will be subscribed to changes of tables from query.
*/
@NonNull
@Override
public Observable<Integer> createObservable() {
throwExceptionIfRxJavaIsNotAvailable("createObservable()");

return storIOContentResolver
.observeChangesOfUri(query.uri()) // each change triggers executeAsBlocking
.map(MapSomethingToExecuteAsBlocking.newInstance(this))
.startWith(Observable.create(OnSubscribeExecuteAsBlocking.newInstance(this))) // start stream with first query result
.subscribeOn(Schedulers.io());
}

/**
* Builder for {@link PreparedGetNumberOfResults}.
*/
public static final class Builder {

@NonNull
private final StorIOContentResolver storIOContentResolver;

Builder(@NonNull StorIOContentResolver storIOContentResolver) {
this.storIOContentResolver = storIOContentResolver;
}

/**
* Required: Specifies query which will be passed to {@link StorIOContentResolver}
* to get list of objects.
*
* @param query non-null query.
* @return builder.
* @see Query
*/
@NonNull
public CompleteBuilder withQuery(@NonNull Query query) {
checkNotNull(query, "Please specify query");
return new CompleteBuilder(storIOContentResolver, query);
}
}

/**
* Compile-time safe part of builder for {@link PreparedGetNumberOfResults}.
*/
public static final class CompleteBuilder {

@NonNull
static final GetResolver<Integer> STANDARD_GET_RESOLVER = new DefaultGetResolver<Integer>() {
@NonNull
@Override
public Integer mapFromCursor(@NonNull Cursor cursor) {
return cursor.getCount();
}
};

@NonNull
private final StorIOContentResolver storIOContentResolver;

@NonNull
Query query;

@Nullable
private GetResolver<Integer> getResolver;

CompleteBuilder(@NonNull StorIOContentResolver storIOContentResolver, @NonNull Query query) {
this.storIOContentResolver = storIOContentResolver;
this.query = query;
}

/**
* Optional: Specifies resolver for Get Operation which can be used
* to provide custom behavior of Get Operation.
* <p>
*
* @param getResolver nullable resolver for Get Operation.
* @return builder.
*/
@NonNull
public CompleteBuilder withGetResolver(@Nullable GetResolver<Integer> getResolver) {
this.getResolver = getResolver;
return this;
}

/**
* Builds new instance of {@link PreparedGetNumberOfResults}.
*
* @return new instance of {@link PreparedGetNumberOfResults}.
*/
@NonNull
public PreparedGetNumberOfResults prepare() {
if (getResolver == null) {
getResolver = STANDARD_GET_RESOLVER;
}

return new PreparedGetNumberOfResults(
storIOContentResolver,
query,
getResolver
);
}
}

}
Expand Up @@ -165,4 +165,34 @@ public void getListOfObjectsAsObservableOnlyInitialValue() {
changesTestSubscriber.assertNoErrors();
changesTestSubscriber.assertValues(Changes.newInstance(TestItem.CONTENT_URI));
}

@Test
public void getNumberOfResults() {
TestSubscriber<Changes> changesTestSubscriber = new TestSubscriber<Changes>();

storIOContentResolver
.observeChangesOfUri(TestItem.CONTENT_URI)
.take(1)
.subscribe(changesTestSubscriber);

for (int i = 0; i < 8; ++i) {
TestItem testItemToInsert = TestItem.create(null, "value");
contentResolver.insert(TestItem.CONTENT_URI, testItemToInsert.toContentValues());
}

Integer numberOfResults = storIOContentResolver
.get()
.numberOfResults()
.withQuery(Query.builder()
.uri(TestItem.CONTENT_URI)
.build())
.prepare()
.executeAsBlocking();

assertThat(numberOfResults).isEqualTo(8);

changesTestSubscriber.awaitTerminalEvent(60, SECONDS);
changesTestSubscriber.assertNoErrors();
changesTestSubscriber.assertValue(Changes.newInstance(TestItem.CONTENT_URI));
}
}
@@ -0,0 +1,100 @@
package com.pushtorefresh.storio.contentresolver.operations.get;

import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;

import com.pushtorefresh.storio.contentresolver.Changes;
import com.pushtorefresh.storio.contentresolver.StorIOContentResolver;
import com.pushtorefresh.storio.contentresolver.queries.Query;
import com.pushtorefresh.storio.test.ObservableBehaviorChecker;

import rx.Observable;
import rx.functions.Action1;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

public class GetNumberOfResultsStub {
@NonNull
final StorIOContentResolver storIOContentResolver;

@NonNull
private final StorIOContentResolver.Internal internal;

@NonNull
final Query query;

@NonNull
final GetResolver<Integer> getResolverForNumberOfResults;

@NonNull
private final Cursor cursor;

@NonNull
private final Integer numberOfResults;

@SuppressWarnings("unchecked")
private GetNumberOfResultsStub() {
storIOContentResolver = mock(StorIOContentResolver.class);
internal = mock(StorIOContentResolver.Internal.class);

when(storIOContentResolver.internal())
.thenReturn(internal);

query = Query
.builder()
.uri(mock(Uri.class))
.build();

getResolverForNumberOfResults = mock(GetResolver.class);
cursor = mock(Cursor.class);

numberOfResults = 129;

when(storIOContentResolver.get())
.thenReturn(new PreparedGet.Builder(storIOContentResolver));

when(storIOContentResolver.observeChangesOfUri(eq(query.uri())))
.thenReturn(Observable.<Changes>empty());

when(getResolverForNumberOfResults.performGet(storIOContentResolver, query))
.thenReturn(cursor);

when(getResolverForNumberOfResults.mapFromCursor(cursor))
.thenReturn(numberOfResults);
}

@NonNull
static GetNumberOfResultsStub newInstance() {
return new GetNumberOfResultsStub();
}

void verifyQueryBehaviorForInteger(@NonNull Integer actualNumberOfResults) {
assertThat(actualNumberOfResults).isNotNull();
verify(storIOContentResolver).get();
verify(getResolverForNumberOfResults).performGet(storIOContentResolver, query);
assertThat(actualNumberOfResults).isSameAs(numberOfResults);
verify(cursor).close();
verifyNoMoreInteractions(storIOContentResolver, internal, cursor);
}

void verifyQueryBehaviorForInteger(@NonNull Observable<Integer> observable) {
new ObservableBehaviorChecker<Integer>()
.observable(observable)
.expectedNumberOfEmissions(1)
.testAction(new Action1<Integer>() {
@Override
public void call(Integer numberOfResults) {
// Get Operation should be subscribed to changes of tables from Query
verify(storIOContentResolver).observeChangesOfUri(eq(query.uri()));
verifyQueryBehaviorForInteger(numberOfResults);
}
})
.checkBehaviorOfObservable();
}
}

0 comments on commit 42b626d

Please sign in to comment.