com.google.firebase.database.core.view.QueryParams Maven / Gradle / Ivy
/*
* 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.core.view;
import static com.google.firebase.database.snapshot.NodeUtilities.NodeFromJSON;
import com.google.firebase.database.core.view.filter.IndexedFilter;
import com.google.firebase.database.core.view.filter.LimitedFilter;
import com.google.firebase.database.core.view.filter.NodeFilter;
import com.google.firebase.database.core.view.filter.RangedFilter;
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.LongNode;
import com.google.firebase.database.snapshot.Node;
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.util.JsonMapper;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class QueryParams {
public static final QueryParams DEFAULT_PARAMS = new QueryParams();
private static final String INDEX_START_VALUE = "sp";
private static final String INDEX_START_NAME = "sn";
private static final String INDEX_END_VALUE = "ep";
private static final String INDEX_END_NAME = "en";
private static final String LIMIT = "l";
private static final String VIEW_FROM = "vf";
private static final String INDEX = "i";
private Integer limit;
private ViewFrom viewFrom;
private Node indexStartValue = null;
private ChildKey indexStartName = null;
private Node indexEndValue = null;
private ChildKey indexEndName = null;
private Index index = PriorityIndex.getInstance();
private String jsonSerialization = null;
public static QueryParams fromQueryObject(Map map) {
QueryParams params = new QueryParams();
params.limit = (Integer) map.get(LIMIT);
if (map.containsKey(INDEX_START_VALUE)) {
Object indexStartValue = map.get(INDEX_START_VALUE);
params.indexStartValue = normalizeValue(NodeFromJSON(indexStartValue));
String indexStartName = (String) map.get(INDEX_START_NAME);
if (indexStartName != null) {
params.indexStartName = ChildKey.fromString(indexStartName);
}
}
if (map.containsKey(INDEX_END_VALUE)) {
Object indexEndValue = map.get(INDEX_END_VALUE);
params.indexEndValue = normalizeValue(NodeFromJSON(indexEndValue));
String indexEndName = (String) map.get(INDEX_END_NAME);
if (indexEndName != null) {
params.indexEndName = ChildKey.fromString(indexEndName);
}
}
String viewFrom = (String) map.get(VIEW_FROM);
if (viewFrom != null) {
params.viewFrom = viewFrom.equals("l") ? ViewFrom.LEFT : ViewFrom.RIGHT;
}
String indexStr = (String) map.get(INDEX);
if (indexStr != null) {
params.index = Index.fromQueryDefinition(indexStr);
}
return params;
}
private static Node normalizeValue(Node value) {
if (value instanceof StringNode
|| value instanceof BooleanNode
|| value instanceof DoubleNode
|| value instanceof EmptyNode) {
return value;
} else if (value instanceof LongNode) {
// We normalize longs to doubles. This is *ESSENTIAL* to prevent our persistence
// code from breaking, since integer-valued doubles get turned into longs after being
// saved to persistence (as JSON) and then read back. (see http://b/30153920/)
return new DoubleNode(
((Long) value.getValue()).doubleValue(), PriorityUtilities.NullPriority());
} else {
throw new IllegalStateException(
"Unexpected value passed to normalizeValue: " + value.getValue());
}
}
public boolean hasStart() {
return indexStartValue != null;
}
public Node getIndexStartValue() {
if (!hasStart()) {
throw new IllegalArgumentException(
"Cannot get index start value if start has not " + "been set");
}
return indexStartValue;
}
public ChildKey getIndexStartName() {
if (!hasStart()) {
throw new IllegalArgumentException(
"Cannot get index start name if start has not been" + " set");
}
if (indexStartName != null) {
return indexStartName;
} else {
return ChildKey.getMinName();
}
}
public boolean hasEnd() {
return indexEndValue != null;
}
public Node getIndexEndValue() {
if (!hasEnd()) {
throw new IllegalArgumentException(
"Cannot get index end value if start has not been " + "set");
}
return indexEndValue;
}
public ChildKey getIndexEndName() {
if (!hasEnd()) {
throw new IllegalArgumentException(
"Cannot get index end name if start has not been " + "set");
}
if (indexEndName != null) {
return indexEndName;
} else {
return ChildKey.getMaxName();
}
}
public boolean hasLimit() {
return limit != null;
}
public boolean hasAnchoredLimit() {
return hasLimit() && this.viewFrom != null;
}
public int getLimit() {
if (!hasLimit()) {
throw new IllegalArgumentException("Cannot get limit if limit has not been set");
}
return this.limit;
}
public Index getIndex() {
return this.index;
}
private QueryParams copy() {
QueryParams params = new QueryParams();
params.limit = limit;
params.indexStartValue = indexStartValue;
params.indexStartName = indexStartName;
params.indexEndValue = indexEndValue;
params.indexEndName = indexEndName;
params.viewFrom = viewFrom;
params.index = index;
return params;
}
public QueryParams limitToFirst(int limit) {
QueryParams copy = copy();
copy.limit = limit;
copy.viewFrom = ViewFrom.LEFT;
return copy;
}
public QueryParams limitToLast(int limit) {
QueryParams copy = copy();
copy.limit = limit;
copy.viewFrom = ViewFrom.RIGHT;
return copy;
}
public QueryParams startAt(Node indexStartValue, ChildKey indexStartName) {
assert indexStartValue.isLeafNode() || indexStartValue.isEmpty();
QueryParams copy = copy();
copy.indexStartValue = indexStartValue;
copy.indexStartName = indexStartName;
return copy;
}
public QueryParams endAt(Node indexEndValue, ChildKey indexEndName) {
assert indexEndValue.isLeafNode() || indexEndValue.isEmpty();
QueryParams copy = copy();
copy.indexEndValue = indexEndValue;
copy.indexEndName = indexEndName;
return copy;
}
public QueryParams orderBy(Index index) {
QueryParams copy = copy();
copy.index = index;
return copy;
}
public boolean isViewFromLeft() {
return this.viewFrom != null ? this.viewFrom == ViewFrom.LEFT : hasStart();
}
// NOTE: Don't change this unless you're changing the wire protocol!
public Map getWireProtocolParams() {
Map queryObject = new HashMap<>();
if (hasStart()) {
queryObject.put(INDEX_START_VALUE, indexStartValue.getValue());
if (indexStartName != null) {
queryObject.put(INDEX_START_NAME, indexStartName.asString());
}
}
if (hasEnd()) {
queryObject.put(INDEX_END_VALUE, indexEndValue.getValue());
if (indexEndName != null) {
queryObject.put(INDEX_END_NAME, indexEndName.asString());
}
}
if (limit != null) {
queryObject.put(LIMIT, limit);
ViewFrom viewFromToAdd = viewFrom;
if (viewFromToAdd == null) {
// limit(), rather than limitToFirst or limitToLast was called.
// This means that only one of hasStart() and hasEnd() is true. Use them
// to calculate which side of the view to anchor to. If neither is set,
// anchor to the end.
if (hasStart()) {
viewFromToAdd = ViewFrom.LEFT;
} else {
// endSet_ or neither set
viewFromToAdd = ViewFrom.RIGHT;
}
}
// CSOFF: MissingSwitchDefaultCheck
switch (viewFromToAdd) {
case LEFT:
queryObject.put(VIEW_FROM, "l");
break;
case RIGHT:
queryObject.put(VIEW_FROM, "r");
break;
}
// CSON: MissingSwitchDefaultCheck
}
if (!index.equals(PriorityIndex.getInstance())) {
queryObject.put(INDEX, index.getQueryDefinition());
}
return queryObject;
}
public boolean loadsAllData() {
return !(hasStart() || hasEnd() || hasLimit());
}
public boolean isDefault() {
return loadsAllData() && index.equals(PriorityIndex.getInstance());
}
public boolean isValid() {
return !(hasStart() && hasEnd() && hasLimit() && !hasAnchoredLimit());
}
public String toJSON() {
if (jsonSerialization == null) {
try {
jsonSerialization = JsonMapper.serializeJson(getWireProtocolParams());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return jsonSerialization;
}
public NodeFilter getNodeFilter() {
if (this.loadsAllData()) {
return new IndexedFilter(this.getIndex());
} else if (this.hasLimit()) {
return new LimitedFilter(this);
} else {
return new RangedFilter(this);
}
}
@Override
public String toString() {
return getWireProtocolParams().toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
QueryParams that = (QueryParams) o;
if (limit != null ? !limit.equals(that.limit) : that.limit != null) {
return false;
}
if (index != null ? !index.equals(that.index) : that.index != null) {
return false;
}
if (indexEndName != null
? !indexEndName.equals(that.indexEndName)
: that.indexEndName != null) {
return false;
}
if (indexEndValue != null
? !indexEndValue.equals(that.indexEndValue)
: that.indexEndValue != null) {
return false;
}
if (indexStartName != null
? !indexStartName.equals(that.indexStartName)
: that.indexStartName != null) {
return false;
}
if (indexStartValue != null
? !indexStartValue.equals(that.indexStartValue)
: that.indexStartValue != null) {
return false;
}
// viewFrom might be null, but we really want to compare left vs right
if (isViewFromLeft() != that.isViewFromLeft()) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = limit != null ? limit : 0;
result = 31 * result + (isViewFromLeft() ? 1231 : 1237);
result = 31 * result + (indexStartValue != null ? indexStartValue.hashCode() : 0);
result = 31 * result + (indexStartName != null ? indexStartName.hashCode() : 0);
result = 31 * result + (indexEndValue != null ? indexEndValue.hashCode() : 0);
result = 31 * result + (indexEndName != null ? indexEndName.hashCode() : 0);
result = 31 * result + (index != null ? index.hashCode() : 0);
return result;
}
private enum ViewFrom {
LEFT,
RIGHT
}
}