Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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;
import static com.google.firebase.database.utilities.Utilities.hardAssert;
import com.google.firebase.database.core.ChildEventRegistration;
import com.google.firebase.database.core.EventRegistration;
import com.google.firebase.database.core.Path;
import com.google.firebase.database.core.Repo;
import com.google.firebase.database.core.ValueEventRegistration;
import com.google.firebase.database.core.ZombieEventManager;
import com.google.firebase.database.core.view.QueryParams;
import com.google.firebase.database.core.view.QuerySpec;
import com.google.firebase.database.snapshot.BooleanNode;
import com.google.firebase.database.snapshot.ChildKey;
import com.google.firebase.database.snapshot.DoubleNode;
import com.google.firebase.database.snapshot.EmptyNode;
import com.google.firebase.database.snapshot.Index;
import com.google.firebase.database.snapshot.KeyIndex;
import com.google.firebase.database.snapshot.Node;
import com.google.firebase.database.snapshot.PathIndex;
import com.google.firebase.database.snapshot.PriorityIndex;
import com.google.firebase.database.snapshot.PriorityUtilities;
import com.google.firebase.database.snapshot.StringNode;
import com.google.firebase.database.snapshot.ValueIndex;
import com.google.firebase.database.utilities.Validation;
/**
* The Query class (and its subclass, {@link DatabaseReference}) are used for reading data.
* Listeners are attached, and they will be triggered when the corresponding data changes.
*
* Instances of Query are obtained by calling startAt(), endAt(), or limit() on a DatabaseReference.
*/
public class Query {
/**
* @hide
*/
protected final Repo repo;
/**
* @hide
*/
protected final Path path;
/**
* @hide
*/
protected final QueryParams params;
// we can't use params index, because the default query params have priority index set as
// default,
// but we don't want to allow multiple orderByPriority calls, so track them here
private final boolean orderByCalled;
Query(Repo repo, Path path, QueryParams params, boolean orderByCalled) throws DatabaseException {
hardAssert(params.isValid(), "Validation of queries failed.");
this.repo = repo;
this.path = path;
this.params = params;
this.orderByCalled = orderByCalled;
}
Query(Repo repo, Path path) {
this(repo, path, QueryParams.DEFAULT_PARAMS, false);
}
/**
* This method validates that key index has been called with the correct combination of
* parameters.
*/
private void validateQueryEndpoints(QueryParams params) {
if (params.getIndex().equals(KeyIndex.getInstance())) {
String message =
"You must use startAt(String value), endAt(String value) or "
+ "equalTo(String value) in combination with orderByKey(). Other type of values or "
+ "using the version with 2 parameters is not supported";
if (params.hasStart()) {
Node startNode = params.getIndexStartValue();
ChildKey startName = params.getIndexStartName();
if (startName != ChildKey.getMinName() || !(startNode instanceof StringNode)) {
throw new IllegalArgumentException(message);
}
}
if (params.hasEnd()) {
Node endNode = params.getIndexEndValue();
ChildKey endName = params.getIndexEndName();
if (endName != ChildKey.getMaxName() || !(endNode instanceof StringNode)) {
throw new IllegalArgumentException(message);
}
}
} else if (params.getIndex().equals(PriorityIndex.getInstance())) {
if ((params.hasStart() && !PriorityUtilities.isValidPriority(params.getIndexStartValue()))
|| (params.hasEnd() && !PriorityUtilities.isValidPriority(params.getIndexEndValue()))) {
throw new IllegalArgumentException(
"When using orderByPriority(), values provided to startAt(), "
+ "endAt(), or equalTo() must be valid priorities.");
}
}
}
/**
* This method validates that limit has been called with the correct combination or parameters.
*/
private void validateLimit(QueryParams params) {
if (params.hasStart() && params.hasEnd() && params.hasLimit() && !params.hasAnchoredLimit()) {
throw new IllegalArgumentException(
"Can't combine startAt(), endAt() and limit(). "
+ "Use limitToFirst() or limitToLast() instead");
}
}
/** This method validates that the equalTo call can be made. */
private void validateEqualToCall() {
if (params.hasStart()) {
throw new IllegalArgumentException("Can't call equalTo() and startAt() combined");
}
if (params.hasEnd()) {
throw new IllegalArgumentException("Can't call equalTo() and endAt() combined");
}
}
/** This method validates that only one order by call has been made. */
private void validateNoOrderByCall() {
if (this.orderByCalled) {
throw new IllegalArgumentException("You can't combine multiple orderBy calls!");
}
}
/**
* Add a listener for changes in the data at this location. Each time time the data changes, your
* listener will be called with an immutable snapshot of the data.
*
* @param listener The listener to be called with changes
* @return A reference to the listener provided. Save this to remove the listener later.
*/
public ValueEventListener addValueEventListener(ValueEventListener listener) {
addEventRegistration(new ValueEventRegistration(repo, listener, getSpec()));
return listener;
}
/**
* Add a listener for child events occurring at this location. When child locations are added,
* removed, changed, or moved, the listener will be triggered for the appropriate event.
*
* @param listener The listener to be called with changes
* @return A reference to the listener provided. Save this to remove the listener later.
*/
public ChildEventListener addChildEventListener(ChildEventListener listener) {
addEventRegistration(new ChildEventRegistration(repo, listener, getSpec()));
return listener;
}
/**
* Add a listener for a single change in the data at this location. This listener will be
* triggered once with the value of the data at the location.
*
* @param listener The listener to be called with the data
*/
public void addListenerForSingleValueEvent(final ValueEventListener listener) {
addEventRegistration(
new ValueEventRegistration(
repo,
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
// Removing the event listener will also prevent any further calls into
// onDataChange
removeEventListener(this);
listener.onDataChange(snapshot);
}
@Override
public void onCancelled(DatabaseError error) {
listener.onCancelled(error);
}
},
getSpec()));
}
/**
* Remove the specified listener from this location.
*
* @param listener The listener to remove
*/
public void removeEventListener(final ValueEventListener listener) {
if (listener == null) {
throw new NullPointerException("listener must not be null");
}
removeEventRegistration(new ValueEventRegistration(repo, listener, getSpec()));
}
/**
* Remove the specified listener from this location.
*
* @param listener The listener to remove
*/
public void removeEventListener(final ChildEventListener listener) {
if (listener == null) {
throw new NullPointerException("listener must not be null");
}
removeEventRegistration(new ChildEventRegistration(repo, listener, getSpec()));
}
private void removeEventRegistration(final EventRegistration registration) {
ZombieEventManager.getInstance().zombifyForRemove(registration);
repo.scheduleNow(
new Runnable() {
@Override
public void run() {
repo.removeEventCallback(registration);
}
});
}
private void addEventRegistration(final EventRegistration listener) {
ZombieEventManager.getInstance().recordEventRegistration(listener);
repo.scheduleNow(
new Runnable() {
@Override
public void run() {
repo.addEventCallback(listener);
}
});
}
/**
* By calling {@code keepSynced(true)} on a location, the data for that location will
* automatically be downloaded and kept in sync, even when no listeners are attached for that
* location. Additionally, while a location is kept synced, it will not be evicted from the
* persistent disk cache.
*
* @param keepSynced Pass {@code true} to keep this location synchronized, pass {@code false} to
* stop synchronization.
* @since 2.3
*/
public void keepSynced(final boolean keepSynced) {
if (!this.path.isEmpty() && this.path.getFront().equals(ChildKey.getInfoKey())) {
throw new DatabaseException("Can't call keepSynced() on .info paths.");
}
repo.scheduleNow(
new Runnable() {
@Override
public void run() {
repo.keepSynced(getSpec(), keepSynced);
}
});
}
/* Removes all of the event listeners at this location */
/*public void removeAllEventListeners() {
Query.scheduleNow(new Runnable() {
@Override
public void run() {
repo.removeEventCallback(Query.this, null);
}
});
}*/
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A Query with the new constraint
*/
public Query startAt(String value) {
return startAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A Query with the new constraint
*/
public Query startAt(double value) {
return startAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A Query with the new constraint
* @since 2.0
*/
public Query startAt(boolean value) {
return startAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key greater than or equal to the given key.
*
* @param value The priority to start at, inclusive
* @param key The key to start at, inclusive
* @return A Query with the new constraint
*/
public Query startAt(String value, String key) {
Node node =
value != null ? new StringNode(value, PriorityUtilities.NullPriority()) : EmptyNode.Empty();
return startAt(node, key);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key greater than or equal to the given key.
*
* @param value The priority to start at, inclusive
* @param key The key name to start at, inclusive
* @return A Query with the new constraint
*/
public Query startAt(double value, String key) {
return startAt(new DoubleNode(value, PriorityUtilities.NullPriority()), key);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key greater than or equal to the given key.
*
* @param value The priority to start at, inclusive
* @param key The key to start at, inclusive
* @return A Query with the new constraint
* @since 2.0
*/
public Query startAt(boolean value, String key) {
return startAt(new BooleanNode(value, PriorityUtilities.NullPriority()), key);
}
private Query startAt(Node node, String key) {
Validation.validateNullableKey(key);
if (!(node.isLeafNode() || node.isEmpty())) {
throw new IllegalArgumentException("Can only use simple values for startAt()");
}
if (params.hasStart()) {
throw new IllegalArgumentException("Can't call startAt() or equalTo() multiple times");
}
ChildKey childKey = key != null ? ChildKey.fromString(key) : null;
QueryParams newParams = params.startAt(node, childKey);
validateLimit(newParams);
validateQueryEndpoints(newParams);
assert newParams.isValid();
return new Query(repo, path, newParams, orderByCalled);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A Query with the new constraint
*/
public Query endAt(String value) {
return endAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A Query with the new constraint
*/
public Query endAt(double value) {
return endAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A Query with the new constraint
* @since 2.0
*/
public Query endAt(boolean value) {
return endAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key key less than or equal to the given key.
*
* @param value The value to end at, inclusive
* @param key The key to end at, inclusive
* @return A Query with the new constraint
*/
public Query endAt(String value, String key) {
Node node =
value != null ? new StringNode(value, PriorityUtilities.NullPriority()) : EmptyNode.Empty();
return endAt(node, key);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key less than or equal to the given key.
*
* @param value The value to end at, inclusive
* @param key The key to end at, inclusive
* @return A Query with the new constraint
*/
public Query endAt(double value, String key) {
return endAt(new DoubleNode(value, PriorityUtilities.NullPriority()), key);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key less than or equal to the given key.
*
* @param value The value to end at, inclusive
* @param key The key to end at, inclusive
* @return A Query with the new constraint
* @since 2.0
*/
public Query endAt(boolean value, String key) {
return endAt(new BooleanNode(value, PriorityUtilities.NullPriority()), key);
}
private Query endAt(Node node, String key) {
Validation.validateNullableKey(key);
if (!(node.isLeafNode() || node.isEmpty())) {
throw new IllegalArgumentException("Can only use simple values for endAt()");
}
ChildKey childKey = key != null ? ChildKey.fromString(key) : null;
if (params.hasEnd()) {
throw new IllegalArgumentException("Can't call endAt() or equalTo() multiple times");
}
QueryParams newParams = params.endAt(node, childKey);
validateLimit(newParams);
validateQueryEndpoints(newParams);
assert newParams.isValid();
return new Query(repo, path, newParams, orderByCalled);
}
/**
* Create a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
*/
public Query equalTo(String value) {
validateEqualToCall();
return this.startAt(value).endAt(value);
}
/**
* Create a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
*/
public Query equalTo(double value) {
validateEqualToCall();
return this.startAt(value).endAt(value);
}
/**
* Create a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
* @since 2.0
*/
public Query equalTo(boolean value) {
validateEqualToCall();
return this.startAt(value).endAt(value);
}
/**
* Create a query constrained to only return the child node with the given key and value. Note
* that there is at most one such child as names are unique.
*
* @param value The value to query for
* @param key The key of the child
* @return A query with the new constraint
*/
public Query equalTo(String value, String key) {
validateEqualToCall();
return this.startAt(value, key).endAt(value, key);
}
/**
* Create a query constrained to only return the child node with the given key and value. Note
* that there is at most one such child as keys are unique.
*
* @param value The value to query for
* @param key The key of the child
* @return A query with the new constraint
*/
public Query equalTo(double value, String key) {
validateEqualToCall();
return this.startAt(value, key).endAt(value, key);
}
/**
* Create a query constrained to only return the child node with the given key and value. Note
* that there is at most one such child as keys are unique.
*
* @param value The value to query for
* @param key The name of the child
* @return A query with the new constraint
*/
public Query equalTo(boolean value, String key) {
validateEqualToCall();
return this.startAt(value, key).endAt(value, key);
}
/**
* Create a query with limit and anchor it to the start of the window.
*
* @param limit The maximum number of child nodes to return
* @return A Query with the new constraint
* @since 2.0
*/
public Query limitToFirst(int limit) {
if (limit <= 0) {
throw new IllegalArgumentException("Limit must be a positive integer!");
}
if (params.hasLimit()) {
throw new IllegalArgumentException(
"Can't call limitToLast on query with previously set limit!");
}
return new Query(repo, path, params.limitToFirst(limit), orderByCalled);
}
/**
* Create a query with limit and anchor it to the end of the window.
*
* @param limit The maximum number of child nodes to return
* @return A Query with the new constraint
* @since 2.0
*/
public Query limitToLast(int limit) {
if (limit <= 0) {
throw new IllegalArgumentException("Limit must be a positive integer!");
}
if (params.hasLimit()) {
throw new IllegalArgumentException(
"Can't call limitToLast on query with previously set limit!");
}
return new Query(repo, path, params.limitToLast(limit), orderByCalled);
}
/**
* Create a query in which child nodes are ordered by the values of the specified path.
*
* @param path The path to the child node to use for sorting
* @return A Query with the new constraint
* @since 2.0
*/
public Query orderByChild(String path) {
if (path == null) {
throw new NullPointerException("Key can't be null");
}
if (path.equals("$key") || path.equals(".key")) {
throw new IllegalArgumentException(
"Can't use '" + path + "' as path, please use orderByKey() instead!");
}
if (path.equals("$priority") || path.equals(".priority")) {
throw new IllegalArgumentException(
"Can't use '" + path + "' as path, please use orderByPriority() instead!");
}
if (path.equals("$value") || path.equals(".value")) {
throw new IllegalArgumentException(
"Can't use '" + path + "' as path, please use orderByValue() instead!");
}
Validation.validatePathString(path);
validateNoOrderByCall();
Path indexPath = new Path(path);
if (indexPath.size() == 0) {
throw new IllegalArgumentException("Can't use empty path, use orderByValue() instead!");
}
Index index = new PathIndex(indexPath);
return new Query(repo, this.path, params.orderBy(index), true);
}
/**
* Create a query in which child nodes are ordered by their priorities.
*
* @return A Query with the new constraint
* @since 2.0
*/
public Query orderByPriority() {
validateNoOrderByCall();
QueryParams newParams = params.orderBy(PriorityIndex.getInstance());
validateQueryEndpoints(newParams);
return new Query(repo, path, newParams, true);
}
/**
* Create a query in which child nodes are ordered by their keys.
*
* @return A Query with the new constraint
* @since 2.0
*/
public Query orderByKey() {
validateNoOrderByCall();
QueryParams newParams = this.params.orderBy(KeyIndex.getInstance());
validateQueryEndpoints(newParams);
return new Query(repo, path, newParams, true);
}
/**
* Create a query in which nodes are ordered by their value
*
* @return A Query with the new constraint
* @since 2.2
*/
public Query orderByValue() {
validateNoOrderByCall();
return new Query(repo, path, params.orderBy(ValueIndex.getInstance()), true);
}
/**
* @return A DatabaseReference to this location
*/
public DatabaseReference getRef() {
return new DatabaseReference(repo, getPath());
}
// Need to hide these...
/**
* For internal use
*
* @hide
* @return The path to this location
*/
public Path getPath() {
return path;
}
/**
* For internal use
*
* @hide
* @return The repo
*/
public Repo getRepo() {
return repo;
}
/**
* For internal use
*
* @hide
* @return The constraints
*/
public QuerySpec getSpec() {
return new QuerySpec(path, params);
}
}