/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.firebase.database.future;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.EventRecord;
import com.google.firebase.database.Query;
import com.google.firebase.database.TestFailure;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.database.core.view.Event;
import com.google.firebase.testing.TestUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class ReadFuture implements Future<List<EventRecord>> {
private final ValueEventListener valueEventListener;
private List<EventRecord> events = new ArrayList<>();
private Semaphore semaphore;
private boolean wasCancelled = false;
private boolean done = false;
private Exception exception;
public ReadFuture(final Query ref, final CompletionCondition condition) {
this(ref, condition, false);
}
private ReadFuture(
final Query ref, final CompletionCondition condition, final boolean ignoreFirstNull) {
semaphore = new Semaphore(0);
this.valueEventListener =
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
if (ignoreFirstNull && events.size() == 0 && snapshot.getValue() == null) {
return;
}
events.add(new EventRecord(snapshot, Event.EventType.VALUE, null));
try {
if (condition.isComplete(events)) {
ref.removeEventListener(valueEventListener);
finish();
}
} catch (Exception e) {
exception = e;
finish();
}
}
@Override
public void onCancelled(DatabaseError error) {
wasCancelled = true;
finish();
}
};
ref.addValueEventListener(this.valueEventListener);
}
// Completes on the first fired event
public ReadFuture(final Query ref) {
this(
ref,
new CompletionCondition() {
@Override
public boolean isComplete(List<EventRecord> events) {
return true;
}
});
}
// Factory helper - completes on first non-null value returned.
public static ReadFuture untilNonNull(Query ref) {
return new ReadFuture(
ref,
new CompletionCondition() {
@Override
public boolean isComplete(List<EventRecord> events) {
return events.get(events.size() - 1).getSnapshot().getValue() != null;
}
});
}
// Factory helper - completes when equal to provided value.
public static ReadFuture untilEquals(Query ref, final Object value) {
return untilEquals(ref, value, false);
}
public static ReadFuture untilEquals(Query ref, final Object value, boolean ignoreFirstNull) {
return new ReadFuture(
ref,
new CompletionCondition() {
@Override
public boolean isComplete(List<EventRecord> events) {
Object eventValue = events.get(events.size() - 1).getSnapshot().getValue();
return eventValue != null && eventValue.equals(value);
}
},
ignoreFirstNull);
}
// Factory helper - completes after count events, ignoring the first null
public static ReadFuture untilCountAfterNull(Query ref, final int count) {
return new ReadFuture(
ref,
new CompletionCondition() {
@Override
public boolean isComplete(List<EventRecord> events) {
return events.size() == count;
}
},
true);
}
// Factory helper - completes after count events.
public static ReadFuture untilCount(Query ref, final int count) {
return new ReadFuture(
ref,
new CompletionCondition() {
@Override
public boolean isComplete(List<EventRecord> events) {
return events.size() == count;
}
});
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// Can't cancel this
return false;
}
@Override
public boolean isCancelled() {
return wasCancelled;
}
@Override
public boolean isDone() {
return done; //To change body of implemented methods use File | Settings | File Templates.
}
public Object lastValue() {
return events.get(events.size() - 1).getSnapshot().getValue();
}
@Override
public List<EventRecord> get() throws InterruptedException, ExecutionException {
semaphore.acquire(1);
return events;
}
@Override
public List<EventRecord> get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
public Object waitForLastValue() throws InterruptedException, TimeoutException, TestFailure {
timedWait();
return lastValue();
}
public List<EventRecord> timedGet() throws InterruptedException, TimeoutException, TestFailure {
return timedGet(TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
public List<EventRecord> timedGet(long timeout, TimeUnit timeoutUnit)
throws InterruptedException, TimeoutException, TestFailure {
timedWait(timeout, timeoutUnit);
return events;
}
public void timedWait() throws InterruptedException, TimeoutException, TestFailure {
timedWait(TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
public void timedWait(long timeout, TimeUnit timeoutUnit)
throws InterruptedException, TimeoutException, TestFailure {
if (!semaphore.tryAcquire(1, timeout, timeoutUnit)) {
throw new TimeoutException();
}
if (exception != null) {
throw new TestFailure(exception);
}
}
private void finish() {
done = true;
semaphore.release(1);
}
public interface CompletionCondition {
boolean isComplete(List<EventRecord> events);
}
}