diff --git a/src/main/java/com/google/firebase/database/snapshot/BigDecimalNode.java b/src/main/java/com/google/firebase/database/snapshot/BigDecimalNode.java new file mode 100644 index 000000000..ba57fc4f5 --- /dev/null +++ b/src/main/java/com/google/firebase/database/snapshot/BigDecimalNode.java @@ -0,0 +1,76 @@ +/* + * 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.snapshot; + +import java.math.BigDecimal; + +public class BigDecimalNode extends LeafNode { + + private final String value; + + public BigDecimalNode(BigDecimal value, Node priority) { + this(value == null ? null : value.toString(), priority); + } + + BigDecimalNode(String value, Node priority) { + super(priority); + this.value = value; + } + + @Override + public Node updatePriority(Node priority) { + return new BigDecimalNode(this.value, priority); + } + + @Override + public Object getValue() { + return new BigDecimal(this.value); + } + + @Override + public String getHashRepresentation(HashVersion version) { + return getPriorityHash(version) + "bigdecimal:" + this.value; + } + + @Override + protected LeafType getLeafType() { + return LeafType.Number; + } + + @Override + protected int compareLeafValues(BigDecimalNode other) { + if (other == null) { + return 1; + } + return this.value.compareTo(other.value); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof BigDecimalNode)) { + return false; + } + BigDecimalNode otherBigDecimalNode = (BigDecimalNode) other; + return this.value.equals(otherBigDecimalNode.value) + && this.priority.equals(otherBigDecimalNode.priority); + } + + @Override + public int hashCode() { + return this.value.hashCode() + this.priority.hashCode(); + } +} diff --git a/src/main/java/com/google/firebase/database/snapshot/BigIntegerNode.java b/src/main/java/com/google/firebase/database/snapshot/BigIntegerNode.java new file mode 100644 index 000000000..cf6cb0955 --- /dev/null +++ b/src/main/java/com/google/firebase/database/snapshot/BigIntegerNode.java @@ -0,0 +1,76 @@ +/* + * 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.snapshot; + +import java.math.BigInteger; + +public class BigIntegerNode extends LeafNode { + + private final String value; + + public BigIntegerNode(BigInteger value, Node priority) { + this(value == null ? null : value.toString(), priority); + } + + BigIntegerNode(String value, Node priority) { + super(priority); + this.value = value; + } + + @Override + public Node updatePriority(Node priority) { + return new BigIntegerNode(this.value, priority); + } + + @Override + public Object getValue() { + return new BigInteger(this.value); + } + + @Override + public String getHashRepresentation(HashVersion version) { + return getPriorityHash(version) + "bigdecimal:" + this.value; + } + + @Override + protected LeafType getLeafType() { + return LeafType.Number; + } + + @Override + protected int compareLeafValues(BigIntegerNode other) { + if (other == null) { + return 1; + } + return this.value.compareTo(other.value); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof BigIntegerNode)) { + return false; + } + BigIntegerNode otherBigDecimalNode = (BigIntegerNode) other; + return this.value.equals(otherBigDecimalNode.value) + && this.priority.equals(otherBigDecimalNode.priority); + } + + @Override + public int hashCode() { + return this.value.hashCode() + this.priority.hashCode(); + } +} diff --git a/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java b/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java index 36e1e603c..f15a7f5eb 100644 --- a/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java +++ b/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java @@ -20,6 +20,8 @@ import com.google.firebase.database.collection.ImmutableSortedMap; import com.google.firebase.database.core.ServerValues; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -58,6 +60,10 @@ public static Node NodeFromJSON(Object value, Node priority) throws DatabaseExce return new DoubleNode((Double) value, priority); } else if (value instanceof Boolean) { return new BooleanNode((Boolean) value, priority); + } else if (value instanceof BigDecimal) { + return new BigDecimalNode(((BigDecimal) value), priority); + } else if (value instanceof BigInteger) { + return new BigIntegerNode(((BigInteger) value), priority); } else if (value instanceof Map || value instanceof List) { Map childData; // TODO: refine this and use same code to iterate over array and map by building @@ -99,8 +105,8 @@ public static Node NodeFromJSON(Object value, Node priority) throws DatabaseExce if (childData.isEmpty()) { return EmptyNode.Empty(); } else { - ImmutableSortedMap childSet = - ImmutableSortedMap.Builder.fromMap(childData, ChildrenNode.NAME_ONLY_COMPARATOR); + ImmutableSortedMap childSet = ImmutableSortedMap.Builder + .fromMap(childData, ChildrenNode.NAME_ONLY_COMPARATOR); return new ChildrenNode(childSet, priority); } } else { @@ -113,8 +119,8 @@ public static Node NodeFromJSON(Object value, Node priority) throws DatabaseExce } // CSON: MethodName - public static int nameAndPriorityCompare( - ChildKey key1, Node priority1, ChildKey key2, Node priority2) { + public static int nameAndPriorityCompare(ChildKey key1, Node priority1, ChildKey key2, + Node priority2) { int priCmp = priority1.compareTo(priority2); if (priCmp != 0) { diff --git a/src/main/java/com/google/firebase/database/utilities/encoding/CustomClassMapper.java b/src/main/java/com/google/firebase/database/utilities/encoding/CustomClassMapper.java index 28542c150..28d288c9d 100644 --- a/src/main/java/com/google/firebase/database/utilities/encoding/CustomClassMapper.java +++ b/src/main/java/com/google/firebase/database/utilities/encoding/CustomClassMapper.java @@ -36,6 +36,8 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -44,6 +46,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -190,7 +193,7 @@ private static T deserializeToClass(Object obj, Class clazz) { if (obj == null) { return null; } else if (clazz.isPrimitive() - || Number.class.isAssignableFrom(clazz) + || isPrimitiveNumeric(clazz) || Boolean.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz)) { return deserializeToPrimitive(obj, clazz); @@ -213,6 +216,12 @@ private static T deserializeToClass(Object obj, Class clazz) { return convertBean(obj, clazz); } } + + private static boolean isPrimitiveNumeric(Class clazz) { + return Number.class.isAssignableFrom(clazz) + && !BigDecimal.class.isAssignableFrom(clazz) + && !BigInteger.class.isAssignableFrom(clazz); + } @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) private static T deserializeToParameterizedType(Object obj, ParameterizedType type) { @@ -531,7 +540,8 @@ public BeanMapper(Class clazz) { // Traverse class hierarchy until we reach java.lang.Object which contains a bunch // of fields/getters we don't want to serialize currentClass = currentClass.getSuperclass(); - } while (currentClass != null && !currentClass.equals(Object.class)); + } + while (currentClass != null && !currentClass.equals(Object.class)); if (properties.isEmpty()) { throw new DatabaseException("No properties to serialize found on class " + clazz.getName()); diff --git a/src/test/java/com/google/firebase/database/MapperTest.java b/src/test/java/com/google/firebase/database/MapperTest.java index f6a5892e0..f0097bcd5 100644 --- a/src/test/java/com/google/firebase/database/MapperTest.java +++ b/src/test/java/com/google/firebase/database/MapperTest.java @@ -22,6 +22,9 @@ import static org.junit.Assert.fail; import com.google.firebase.database.utilities.encoding.CustomClassMapper; + +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -30,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import org.junit.Test; public class MapperTest { @@ -248,6 +252,31 @@ public void primitiveDeserializeLong() { } catch (DatabaseException e) { // ignore } } + + @Test + public void bigIntegerDeserialize() { + BigIntegerBean beanBigInteger = deserialize("{'value':'1'}", BigIntegerBean.class); + assertEquals(0,BigInteger.ONE.compareTo(beanBigInteger.getValue())); + + BigIntegerBean beanBigIntegerBig = + deserialize("{'value':'991234569874563214569874563214735'}", + BigIntegerBean.class); + assertEquals(0, new BigInteger("991234569874563214569874563214735") + .compareTo(beanBigIntegerBig.getValue())); + } + + @Test + public void bigDecimalDeserialize() { + BigDecimalBean beanBigDecimal = deserialize("{'value':'1.0'}", + BigDecimalBean.class); + assertEquals(0,BigDecimal.ONE.compareTo(beanBigDecimal.getValue())); + + BigDecimalBean beanBigDecimalBig = + deserialize("{'value':'991234569874563214569874563214735.77874'}", + BigDecimalBean.class); + assertEquals(0, new BigDecimal("991234569874563214569874563214735.77874") + .compareTo(beanBigDecimalBig.getValue())); + } @Test(expected = DatabaseException.class) public void primitiveDeserializeWrongTypeMap() { @@ -1250,6 +1279,22 @@ public int getValue() { } } + private static class BigIntegerBean { + private String value; + + public BigInteger getValue() { + return value == null ? null : new BigInteger(value); + } + } + + private static class BigDecimalBean { + private String value; + + public BigDecimal getValue() { + return value == null ? null : new BigDecimal(value); + } + } + private static class BooleanBean { private boolean value; diff --git a/src/test/java/com/google/firebase/database/snapshot/NodeTest.java b/src/test/java/com/google/firebase/database/snapshot/NodeTest.java index 22e6d6db8..0806bd03a 100644 --- a/src/test/java/com/google/firebase/database/snapshot/NodeTest.java +++ b/src/test/java/com/google/firebase/database/snapshot/NodeTest.java @@ -22,7 +22,11 @@ import com.google.firebase.database.MapBuilder; import com.google.firebase.database.core.Path; + +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Map; + import org.junit.Test; public class NodeTest { @@ -35,6 +39,8 @@ public void getHashWorksCorrectly() { .put("doubleNode", 4.5623) .put("stringNode", "hey guys") .put("boolNode", true) + .put("bigDecimalNode", BigDecimal.ONE) + .put("bigIntegerNode", BigInteger.TEN) .build(); Node node = NodeFromJSON(data); @@ -55,8 +61,16 @@ public void getHashWorksCorrectly() { hash = child.getHash(); assertEquals("E5z61QM0lN/U2WsOnusszCTkR8M=", hash); + child = node.getImmediateChild(ChildKey.fromString("bigDecimalNode")); + hash = child.getHash(); + assertEquals("CsHcc6ldMmpq3mgSeP+Q3XN48Ls=", hash); + + child = node.getImmediateChild(ChildKey.fromString("bigIntegerNode")); + hash = child.getHash(); + assertEquals("9dCcx7ZrW/Ul5TBwZRrABNgawlQ=", hash); + hash = node.getHash(); - assertEquals("6Mc4jFmNdrLVIlJJjz2/MakTK9I=", hash); + assertEquals("G8QKO+3CPOTwU4BIAU0XiPf8oFg=", hash); } @Test @@ -180,4 +194,18 @@ public void nodeFromJsonReturnsEmptyNodesWithoutPriority() { NodeFromJSON(new MapBuilder().put("dummy-node", null).put(".priority", "prio").build()); assertTrue(empty2.getPriority().isEmpty()); } + + @Test + public void bigDecimalsAreConvertedProperly() { + Node node = NodeFromJSON(BigDecimal.ONE); + assertTrue(node instanceof BigDecimalNode); + assertEquals(0, BigDecimal.ONE.compareTo((BigDecimal) node.getValue())); + } + + @Test + public void bigIntegersAreConvertedProperly() { + Node node = NodeFromJSON(BigInteger.TEN); + assertTrue(node instanceof BigIntegerNode); + assertEquals(0, BigInteger.TEN.compareTo((BigInteger) node.getValue())); + } }