Read a numerical data with many decimal places from Cloud Firestore in Kotlin - kotlin

I am trying to read a numeric data from Cloud Firestore. This number contains many decimals and from Kotlin I only get the first whole number. I have tried to fix it with BigDecimal, but it still doesn't work. Can you help me? Thank you.
fun obtenerDatosBD() {
var readBTC:Int
if (email != null) {
db.collection("users").document(email).get().addOnSuccessListener {
if(it.exists()){
readBTC = it.getDouble("BTC")?.toInt()!!
val test = BigDecimal(readBTC)
println("DATOS: $test")
}else{
//No existen datos
db.collection("users").document(email).set(
hashMapOf(
"Saldo" to "0,00",
"BTC" to 0.000000,
"Provider" to provider
)
)
}
}
}
}

You are losing all the decimal points here:
it.getDouble("BTC")?.toInt()!!
You are reading it as Double and then making it an Int. Instead you should try and read it as a String and give that to the BigDecimal constructor.
fun obtenerDatosBD() {
var readBTC: String //Int has no decimal places
if (email != null) {
db.collection("users").document(email).get().addOnSuccessListener {
if (it.exists()) {
readBTC = it.getString("BTC") //or `it.get("BTC") as? String?` or something, I don't know what type of object `it` is
//something to do if readBTC is null, like a return, or have the non existence message in another fun and call it from here as well.
val test = BigDecimal(readBTC)
println("DATOS: $test")
} else {
//No existen datos
db.collection("users").document(email).set(
hashMapOf(
"Saldo" to "0,00",
"BTC" to 0.000000,
"Provider" to provider
)
)
}
}
}
}

fun obtenerDatosBD() {
var readBTC:String
if (email != null) {
db.collection("users").document(email).get().addOnSuccessListener {
if(it.exists()){
readBTC = it.getString("BTC").toString()
val test = BigDecimal(readBTC)
val testInt: Double = test.toDouble()
println("DATOS: $test + $testInt")
}else{
//No existen datos
db.collection("users").document(email).set(
hashMapOf(
"Saldo" to "0,00",
"BTC" to "0.000000",
"Provider" to provider
)
)
}
}
}
}

Related

How to get object of Maximum value from LiveData?

I have liveData of market data. I want one market data object which have highest 'volume'. Here, volume is string value("277927.5793846733451135"), it could be null also.
I am using below code to achieve this. but, its not working.
viewModel.marketlist.observe(this as LifecycleOwner, { marketdata ->
val marketData = marketdata.getOrNull()
if(marketData !=null) {
val mData: MarketData? = marketData.marketData?.maxByOrNull { checkNotNull(it.volume) }
if (mData != null) {
binding.textViewPrice.text = mData.price
}
}
else {
//TODO
}
})
Any help would be appreciated!
You should be able to do something like this:
viewModel.marketList.observe(viewLifecycleOwner) { marketData ->
val maxData = marketData.getOrNull()?.marketData?.let { dataValues ->
dataValues.maxByOrNull { it.volume?.toDoubleOrNull() ?: -1.0 }
}
if (maxData != null) {
binding.textViewPrice.text = maxData.price
}
}
I cleaned up the observe call a bit, then I'm checking if marketData.getOrNull().marketData is null right away with my let { ... } block.
If you do have marketData (the inner one), it'll then safely call maxByOrNull { it.volume }.

SQL - SQLiteDatabaseCorruptException - file is not a database while compiling: PRAGMA journal_mode

I have been working on migrating our DB on a Kotlin Multiplatform project to be encrypted from unencrypted. It's all done on Android, however the iOS part is proving tricky.
Finally I got it somewhat working, however when I return the DB driver, I get this error:
Function doesn't have or inherit #Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError.
It is considered unexpected and unhandled instead. Program will be terminated.
Uncaught Kotlin exception: kotlin.Exception: android/database/sqlite/SQLiteDatabaseCorruptException - file is not a database (code 26): , while compiling: PRAGMA journal_mode
It's strange as it mentions android/database and I'm not sure why.
Anyways for the migration, I have logs setup and I can see that it performs it, and if I debug the app and pull the DB, it does look like the DB has now been encrypted and has old data on it too.
It seems to crash when it gets to this code:
NativeSqliteDriver(DatabaseConfiguration(
name = DatabaseName,
version = AppDatabase.Schema.version,
create = { connection -> wrapConnection(connection) { AppDatabase.Schema.create(it) } },
upgrade = { connection, oldVersion, newVersion ->
try {
wrapConnection(connection) {
NSLog("old version is ${oldVersion} new version is ${newVersion}")
AppDatabase.Schema.migrate(it, oldVersion, newVersion)
}
} catch (exception: Exception) {
NSLog("exception is ${exception.toString()}")
}
}
//Workaround for DatabaseConnection.setCipherKey causing an exception on iOS 14
configConnection = { connection, _ ->
val statement = "PRAGMA key = \"$password\";"
connection.withStatement(statement) {
stringForQuery()
}
}
))
Breakpoints never trigger in the upgrade try/catch.
The migration logic looks like this and is performed before returning the NativeSqlLiteDriver.
#ExperimentalUnsignedTypes
override fun migrateToEncryptedDatabase(databasePath: String, temporaryDatabasePath: String, password: String) {
val fileManager = NSFileManager.defaultManager()
fileManager.createFileAtPath(temporaryDatabasePath, null, null)
if (fileManager.fileExistsAtPath(databasePath)) {
memScoped {
val unencryptedDb: CPointerVar<sqlite3> = allocPointerTo()
val encryptedDb: CPointerVar<sqlite3> = allocPointerTo()
if (sqlite3_open(databasePath, unencryptedDb.ptr) == SQLITE_OK) {
val exec1 = sqlite3_exec(unencryptedDb.value, "ATTACH DATABASE '$temporaryDatabasePath' AS encrypted KEY '$password';", null, null, null)
val exec2 = sqlite3_exec(unencryptedDb.value, "SELECT sqlcipher_export('encrypted')", null, null, null)
val exec3 = sqlite3_exec(unencryptedDb.value, "DETACH DATABASE encrypted;", null, null, null)
val version = sqlite3_version
sqlite3_close(unencryptedDb.value)
if (sqlite3_open(temporaryDatabasePath, encryptedDb.ptr) == SQLITE_OK) {
sqlite3_key(encryptedDb.value, password.cstr, password.cstr.size)
}
sqlite3_close(unencryptedDb.value)
val error: ObjCObjectVar<NSError?> = alloc()
val removeResult = fileManager.removeItemAtPath(databasePath, error.ptr)
if (removeResult == false) {
NSLog("Error removing db file: " + error.value)
} else {
}
val result = fileManager.moveItemAtPath(temporaryDatabasePath, databasePath, error.ptr)
if (result == false) {
NSLog("Error moving db file: " + error.value)
} else {
}
} else {
NSLog("Failed to open the unencrypted DB with message: " + sqlite3_errmsg(unencryptedDb.value))
sqlite3_close(unencryptedDb.value)
}
}
}
}
Thanks for any help
This was actually caused by not updating the version correctly.
When testing, I thought I had updated the version correctly, however it was still returning as 0 instead of 1.
I solved it by doing this.
First a method to retrieve the current DB version:
private fun getUserVersion(unencryptedDBPointer: CPointerVar<sqlite3>): Int? {
memScoped {
val sqliteStatementPointer: CPointerVar<sqlite3_stmt> = allocPointerTo()
var databaseVersion: Int? = null
if (sqlite3_prepare_v2(unencryptedDBPointer.value, "PRAGMA user_version;", USER_VERSION_STATEMENT_MAX_LENGTH, sqliteStatementPointer.ptr, null) == SQLITE_OK) {
while (sqlite3_step(sqliteStatementPointer.value) == SQLITE_ROW) {
databaseVersion = sqlite3_column_int(sqliteStatementPointer.value, COLUMN_TO_USE)
}
} else {
Logger.d("Error preparing the database: ${sqlite3_errmsg(unencryptedDBPointer.value)}")
}
sqlite3_finalize(sqliteStatementPointer.value)
return databaseVersion
}
}
Then, inside my migration method, I had this code:
val dbVersion = getUserVersion(unencryptedDbPointer)
if (sqlite3_open(temporaryDatabasePath, encryptedDbPointer.ptr) == SQLITE_OK && dbVersion != null) {
sqlite3_key(encryptedDbPointer.value, password.cstr, password.cstr.size)
sqlite3_exec(encryptedDbPointer.value, "PRAGMA user_version=$dbVersion", null, null, null)
}
This correctly set the version on the DB and fixed the issue.

max string length in compose before clipping

I want to create a string with the max number of characters allowable in the box.
setContent {
ViewincomposetestTheme {
var size by remember { mutableStateOf(IntSize.Zero) }
var widthdp by remember { mutableStateOf(0.dp) }
BoxWithConstraints(Modifier.fillMaxSize().background(Color.Yellow)) {
val widthOfChar = 13 // how do I find this
var string by remember {
mutableStateOf(
StringBuffer()
.apply {
repeat(maxWidth.value.toInt() / widthOfChar) { append("H") }
}
.toString()
)
}
Text(string)
}
}
}
You can create a separate Text to calculate the size of a character, which will report you its size with onTextLayout. Using drawWithContent you can prevent it from being drawn.
Also in your example you get the width using maxWidth.value.toInt(): here you get the value of dp, not pixels. You could convert it using LocalDensity, but you also get the pixel value directly from BoxWithConstraints using constraints.maxWidth.
BoxWithConstraints(
Modifier
.fillMaxSize()
.background(Color.Yellow)
) {
var charWidth by remember { mutableStateOf<Int?>(null) }
val string = remember(maxWidth, charWidth) {
charWidth?.let { charWidth ->
StringBuffer()
.apply {
repeat(constraints.maxWidth / charWidth) { append("H") }
}
.toString()
}
}
Text(
"H",
onTextLayout = {
charWidth = it.size.width
},
modifier = Modifier.drawWithContent { }
)
string?.let {
Text(string)
}
}
You can just use the maxWidth parameter of the BoxWithConstraints, then convert the obtained dp value toPx(). Decide a textsize for the TextField in sp and then do whatever calculations you want to do after converting that to Px as well. Max characters will be maxWidth / textSize, roughly.

SwiftUI: Is it possible to automatically move to the next textfield after 1 character is entered?

I trying to make a SwiftUI app where after entering one letter in a TextField the cursor automatically moves to the next TextField. The UI is pretty much like this.
In Swift/IB, it looks like this was done with delegates and adding a target like in this post:
How to move to the next UITextField automatically in Swift
But can't find any documentation for using delegates/targets in SwiftUI.
I tried following this post:
SwiftUI TextField max length
But this has not worked for me. Setting the .prefix(1) does not seem to make a difference. The TextField still accepts any amount of characters and when moved to the next TextField does not reduce the characters entered to only the first character.
In SwiftUI's current state, is it possible to automatically move to the next TextField after 1 character is entered?
Thanks for any help!
It can be done in iOS 15 with FocusState
import SwiftUI
///Sample usage
#available(iOS 15.0, *)
struct PinParentView: View {
#State var pin: Int = 12356
var body: some View {
VStack{
Text(pin.description)
PinView(pin: $pin)
}
}
}
#available(iOS 15.0, *)
struct PinView: View {
#Binding var pin: Int
#State var pinDict: [UniqueCharacter] = []
#FocusState private var focusedField: UniqueCharacter?
var body: some View{
HStack{
ForEach($pinDict, id: \.id, content: { $char in
TextField("pin digit", text:
Binding(get: {
char.char.description
}, set: { newValue in
let newest: Character = newValue.last ?? "0"
//This check is only needed if you only want numbers
if Int(newest.description) != nil{
char.char = newest
}
//Set the new focus
DispatchQueue.main.async {
setFocus()
}
})
).textFieldStyle(.roundedBorder)
.focused($focusedField, equals: char)
})
}.onAppear(perform: {
//Set the initial value of the text fields
//By using unique characters you can keep the order
pinDict = pin.description.uniqueCharacters()
})
}
func setFocus(){
//Default to the first box when focus is not set or the user reaches the last box
if focusedField == nil || focusedField == pinDict.last{
focusedField = pinDict.first
}else{
//find the index of the current character
let idx = pinDict.firstIndex(of: focusedField!)
//Another safety check for the index
if idx == nil || pinDict.last == pinDict[idx!]{
focusedField = pinDict.first
}else{
focusedField = pinDict[idx! + 1]
}
}
//Update the Binding that came from the parent
setPinBinding()
}
///Updates the binding from the parent
func setPinBinding(){
var newPinInt = 0
for n in pinDict{
if n == pinDict.first{
newPinInt = Int(n.char.description) ?? 0
}else{
newPinInt = Int(String(newPinInt) + n.char.description) ?? 0
}
}
pin = newPinInt
}
}
//Convert String to Unique characers
extension String{
func uniqueCharacters() -> [UniqueCharacter]{
let array: [Character] = Array(self)
return array.uniqueCharacters()
}
func numberOnly() -> String {
self.trimmingCharacters(in: CharacterSet(charactersIn: "-0123456789.").inverted)
}
}
extension Array where Element == Character {
func uniqueCharacters() -> [UniqueCharacter]{
var array: [UniqueCharacter] = []
for char in self{
array.append(UniqueCharacter(char: char))
}
return array
}
}
//String/Characters can be repeating so yu have to make them a unique value
struct UniqueCharacter: Identifiable, Equatable, Hashable{
var char: Character
var id: UUID = UUID()
}
#available(iOS 15.0, *)
struct PinView_Previews: PreviewProvider {
static var previews: some View {
PinParentView()
}
}

Create a Gson TypeAdapter for a Guava Range

I am trying to serialize Guava Range objects to JSON using Gson, however the default serialization fails, and I'm unsure how to correctly implement a TypeAdapter for this generic type.
Gson gson = new Gson();
Range<Integer> range = Range.closed(10, 20);
String json = gson.toJson(range);
System.out.println(json);
Range<Integer> range2 = gson.fromJson(json,
new TypeToken<Range<Integer>>(){}.getType());
System.out.println(range2);
assertEquals(range2, range);
This fails like so:
{"lowerBound":{"endpoint":10},"upperBound":{"endpoint":20}}
PASSED: typeTokenInterface
FAILED: range
java.lang.RuntimeException: Unable to invoke no-args constructor for
com.google.common.collect.Cut<java.lang.Integer>. Register an
InstanceCreator with Gson for this type may fix this problem.
at com.google.gson.internal.ConstructorConstructor$12.construct(
ConstructorConstructor.java:210)
...
Note that the default serialization actually loses information - it fails to report whether the endpoints are open or closed. I would prefer to see it serialized similar to its toString(), e.g. [10‥20] however simply calling toString() won't work with generic Range instances, as the elements of the range may not be primitives (Joda-Time LocalDate instances, for example). For the same reason, implementing a custom TypeAdapter seems difficult, as we don't know how to deserialize the endpoints.
I've implemented most of a TypeAdaptorFactory based on the template provided for Multimap which ought to work, but now I'm stuck on the generics. Here's what I have so far:
public class RangeTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Range.class
|| !(type instanceof ParameterizedType)) {
return null;
}
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = (TypeAdapter<?>)gson.getAdapter(TypeToken.get(elementType));
// Bound mismatch: The generic method newRangeAdapter(TypeAdapter<E>) of type
// GsonUtils.RangeTypeAdapterFactory is not applicable for the arguments
// (TypeAdapter<capture#4-of ?>). The inferred type capture#4-of ? is not a valid
// substitute for the bounded parameter <E extends Comparable<?>>
return (TypeAdapter<T>) newRangeAdapter(elementAdapter);
}
private <E extends Comparable<?>> TypeAdapter<Range<E>> newRangeAdapter(final TypeAdapter<E> elementAdapter) {
return new TypeAdapter<Range<E>>() {
#Override
public void write(JsonWriter out, Range<E> value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
String repr = (value.lowerBoundType() == BoundType.CLOSED ? "[" : "(") +
(value.hasLowerBound() ? elementAdapter.toJson(value.lowerEndpoint()) : "-\u221e") +
'\u2025' +
(value.hasLowerBound() ? elementAdapter.toJson(value.upperEndpoint()) : "+\u221e") +
(value.upperBoundType() == BoundType.CLOSED ? "]" : ")");
out.value(repr);
}
public Range<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String[] endpoints = in.nextString().split("\u2025");
E lower = elementAdapter.fromJson(endpoints[0].substring(1));
E upper = elementAdapter.fromJson(endpoints[1].substring(0,endpoints[1].length()-1));
return Range.range(lower, endpoints[0].charAt(0) == '[' ? BoundType.CLOSED : BoundType.OPEN,
upper, endpoints[1].charAt(endpoints[1].length()-1) == '[' ? BoundType.CLOSED : BoundType.OPEN);
}
};
}
}
However the return (TypeAdapter<T>) newRangeAdapter(elementAdapter); line has a compilation error and I'm now at a loss.
What's the best way to resolve this error? Is there a better way to serialize Range objects that I'm missing? What about if I want to serialize RangeSets?
Rather frustrating that the Google utility library and Google serialization library seem to require so much glue to work together :(
This feels somewhat like reinventing the wheel, but it was a lot quicker to put together and test than the time spent trying to get Gson to behave, so at least presently I'll be using the following Converters to serialize Range and RangeSet*, rather than Gson.
/**
* Converter between Range instances and Strings, essentially a custom serializer.
* Ideally we'd let Gson or Guava do this for us, but presently this is cleaner.
*/
public static <T extends Comparable<? super T>> Converter<Range<T>, String> rangeConverter(final Converter<T, String> elementConverter) {
final String NEG_INFINITY = "-\u221e";
final String POS_INFINITY = "+\u221e";
final String DOTDOT = "\u2025";
return new Converter<Range<T>, String>() {
#Override
protected String doForward(Range<T> range) {
return (range.hasLowerBound() && range.lowerBoundType() == BoundType.CLOSED ? "[" : "(") +
(range.hasLowerBound() ? elementConverter.convert(range.lowerEndpoint()) : NEG_INFINITY) +
DOTDOT +
(range.hasUpperBound() ? elementConverter.convert(range.upperEndpoint()) : POS_INFINITY) +
(range.hasUpperBound() && range.upperBoundType() == BoundType.CLOSED ? "]" : ")");
}
#Override
protected Range<T> doBackward(String range) {
String[] endpoints = range.split(DOTDOT);
Range<T> ret = Range.all();
if(!endpoints[0].substring(1).equals(NEG_INFINITY)) {
T lower = elementConverter.reverse().convert(endpoints[0].substring(1));
ret = ret.intersection(Range.downTo(lower, endpoints[0].charAt(0) == '[' ? BoundType.CLOSED : BoundType.OPEN));
}
if(!endpoints[1].substring(0,endpoints[1].length()-1).equals(POS_INFINITY)) {
T upper = elementConverter.reverse().convert(endpoints[1].substring(0,endpoints[1].length()-1));
ret = ret.intersection(Range.upTo(upper, endpoints[1].charAt(endpoints[1].length()-1) == ']' ? BoundType.CLOSED : BoundType.OPEN));
}
return ret;
}
};
}
/**
* Converter between RangeSet instances and Strings, essentially a custom serializer.
* Ideally we'd let Gson or Guava do this for us, but presently this is cleaner.
*/
public static <T extends Comparable<? super T>> Converter<RangeSet<T>, String> rangeSetConverter(final Converter<T, String> elementConverter) {
return new Converter<RangeSet<T>, String>() {
private final Converter<Range<T>, String> rangeConverter = rangeConverter(elementConverter);
#Override
protected String doForward(RangeSet<T> rs) {
ArrayList<String> ls = new ArrayList<>();
for(Range<T> range : rs.asRanges()) {
ls.add(rangeConverter.convert(range));
}
return Joiner.on(", ").join(ls);
}
#Override
protected RangeSet<T> doBackward(String rs) {
Iterable<String> parts = Splitter.on(",").trimResults().split(rs);
ImmutableRangeSet.Builder<T> build = ImmutableRangeSet.builder();
for(String range : parts) {
build.add(rangeConverter.reverse().convert(range));
}
return build.build();
}
};
}
*For inter-process communication, Java serialization would likely work just fine, as both classes implement Serializable. However I'm serializing to disk for more permanent storage, meaning I need a format I can trust won't change over time. Guava's serialization doesn't provide that guarantee.
Here is a Gson JsonSerializer and JsonDeserializer that generically supports a Range: https://github.com/jamespedwards42/Fava/wiki/Range-Marshaller
#Override
public JsonElement serialize(final Range src, final Type typeOfSrc, final JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
if ( src.hasLowerBound() ) {
jsonObject.add( "lowerBoundType", context.serialize( src.lowerBoundType() ) );
jsonObject.add( "lowerBound", context.serialize( src.lowerEndpoint() ) );
} else
jsonObject.add( "lowerBoundType", context.serialize( BoundType.OPEN ) );
if ( src.hasUpperBound() ) {
jsonObject.add( "upperBoundType", context.serialize( src.upperBoundType() ) );
jsonObject.add( "upperBound", context.serialize( src.upperEndpoint() ) );
} else
jsonObject.add( "upperBoundType", context.serialize( BoundType.OPEN ) );
return jsonObject;
}
#Override
public Range<? extends Comparable<?>> deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
if ( !( typeOfT instanceof ParameterizedType ) )
throw new IllegalStateException( "typeOfT must be a parameterized Range." );
final JsonObject jsonObject = json.getAsJsonObject();
final JsonElement lowerBoundTypeJsonElement = jsonObject.get( "lowerBoundType" );
final JsonElement upperBoundTypeJsonElement = jsonObject.get( "upperBoundType" );
if ( lowerBoundTypeJsonElement == null || upperBoundTypeJsonElement == null )
throw new IllegalStateException( "Range " + json
+ "was not serialized with this serializer! The default serialization does not store the boundary types, therfore we can not deserialize." );
final Type type = ( ( ParameterizedType ) typeOfT ).getActualTypeArguments()[0];
final BoundType lowerBoundType = context.deserialize( lowerBoundTypeJsonElement, BoundType.class );
final JsonElement lowerBoundJsonElement = jsonObject.get( "lowerBound" );
final Comparable<?> lowerBound = lowerBoundJsonElement == null ? null : context.deserialize( lowerBoundJsonElement, type );
final BoundType upperBoundType = context.deserialize( upperBoundTypeJsonElement, BoundType.class );
final JsonElement upperBoundJsonElement = jsonObject.get( "upperBound" );
final Comparable<?> upperBound = upperBoundJsonElement == null ? null : context.deserialize( upperBoundJsonElement, type );
if ( lowerBound == null && upperBound != null )
return Range.upTo( upperBound, upperBoundType );
else if ( lowerBound != null && upperBound == null )
return Range.downTo( lowerBound, lowerBoundType );
else if ( lowerBound == null && upperBound == null )
return Range.all();
return Range.range( lowerBound, lowerBoundType, upperBound, upperBoundType );
}
Here is a straight forward solution. Works very well
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import com.google.gson.*;
import java.lang.reflect.Type;
public class GoogleRangeAdapter implements JsonSerializer, JsonDeserializer {
public static String TK_hasLowerBound = "hasLowerBound";
public static String TK_hasUpperBound = "hasUpperBound";
public static String TK_lowerBoundType = "lowerBoundType";
public static String TK_upperBoundType = "upperBoundType";
public static String TK_lowerBound = "lowerBound";
public static String TK_upperBound = "upperBound";
#Override
public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = (JsonObject)json;
boolean hasLowerBound = jsonObject.get(TK_hasLowerBound).getAsBoolean();
boolean hasUpperBound = jsonObject.get(TK_hasUpperBound).getAsBoolean();
if (!hasLowerBound && !hasUpperBound) {
return Range.all();
}
else if (!hasLowerBound && hasUpperBound){
double upperBound = jsonObject.get(TK_upperBound).getAsDouble();
BoundType upperBoundType = BoundType.valueOf(jsonObject.get(TK_upperBoundType).getAsString());
if (upperBoundType == BoundType.OPEN)
return Range.lessThan(upperBound);
else
return Range.atMost(upperBound);
}
else if (hasLowerBound && !hasUpperBound){
double lowerBound = jsonObject.get(TK_lowerBound).getAsDouble();
BoundType lowerBoundType = BoundType.valueOf(jsonObject.get(TK_lowerBoundType).getAsString());
if (lowerBoundType == BoundType.OPEN)
return Range.greaterThan(lowerBound);
else
return Range.atLeast(lowerBound);
}
else {
double lowerBound = jsonObject.get(TK_lowerBound).getAsDouble();
double upperBound = jsonObject.get(TK_upperBound).getAsDouble();
BoundType upperBoundType = BoundType.valueOf(jsonObject.get(TK_upperBoundType).getAsString());
BoundType lowerBoundType = BoundType.valueOf(jsonObject.get(TK_lowerBoundType).getAsString());
if (lowerBoundType == BoundType.OPEN && upperBoundType == BoundType.OPEN)
return Range.open(lowerBound, upperBound);
else if (lowerBoundType == BoundType.OPEN && upperBoundType == BoundType.CLOSED)
return Range.openClosed(lowerBound, upperBound);
else if (lowerBoundType == BoundType.CLOSED && upperBoundType == BoundType.OPEN)
return Range.closedOpen(lowerBound, upperBound);
else
return Range.closed(lowerBound, upperBound);
}
}
#Override
public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
Range<Double> range = (Range<Double>)src;
boolean hasLowerBound = range.hasLowerBound();
boolean hasUpperBound = range.hasUpperBound();
jsonObject.addProperty(TK_hasLowerBound, hasLowerBound);
jsonObject.addProperty(TK_hasUpperBound, hasUpperBound);
if (hasLowerBound) {
jsonObject.addProperty(TK_lowerBound, range.lowerEndpoint());
jsonObject.addProperty(TK_lowerBoundType, range.lowerBoundType().name());
}
if (hasUpperBound) {
jsonObject.addProperty(TK_upperBound, range.upperEndpoint());
jsonObject.addProperty(TK_upperBoundType, range.upperBoundType().name());
}
return jsonObject;
}
}