yorkie-ios-sdk/Sources/Document/CRDT/Primitive.swift

187 lines
5.3 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright 2022 The Yorkie Authors. All rights reserved.
*
* 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.
*/
import Foundation
enum PrimitiveValue: Equatable {
case null
case boolean(Bool)
case integer(Int32)
case long(Int64)
case double(Double)
case string(String)
case bytes(Data)
case date(Date)
}
/**
* `Primitive` represents primitive data type including logical clock.
* It has a type and a value.
*/
class Primitive: CRDTElement {
let createdAt: TimeTicket
var movedAt: TimeTicket?
var removedAt: TimeTicket?
let value: PrimitiveValue
init(value: PrimitiveValue, createdAt: TimeTicket) {
self.createdAt = createdAt
switch value {
case .date(let dateValue):
// Trim the less than a millisecond
self.value = .date(dateValue.trimedLessThanMilliseconds)
default:
self.value = value
}
}
/**
* `type` returns the primitive type of the value.
*/
static func type(of value: Any?) -> PrimitiveValue? {
guard let value else {
return .null
}
switch value {
case let casted as Bool:
return .boolean(casted)
case let casted as Int32:
return .integer(casted)
case let casted as Int64:
return .long(casted)
case let casted as Int:
return .long(Int64(casted))
case let casted as Double:
return .double(casted)
case let casted as String:
return .string(casted)
case let casted as Data:
return .bytes(casted)
case let casted as Date:
return .date(casted)
default:
return nil
}
}
/**
* `isSupport` check if the given value is supported type.
*/
static func isSupport(value: Any) -> Bool {
return Primitive.type(of: value) != nil
}
/**
* `isNumericType` checks numeric type by JSONPrimitive
*/
var isNumericType: Bool {
switch self.value {
case .integer, .long, .double:
return true
default:
return false
}
}
/**
* `toBytes` creates an array representing the value.
*/
func toBytes() -> Data {
switch self.value {
case .null:
return Data()
case .boolean(let value):
var valueInInt = Int(exactly: NSNumber(value: value))
return Data(bytes: &valueInInt, count: MemoryLayout.size(ofValue: valueInInt))
case .integer(let value):
return withUnsafeBytes(of: value.littleEndian) { Data($0) }
case .double(let value):
return withUnsafeBytes(of: value.bitPattern.littleEndian) { Data($0) }
case .string(let value):
return value.data(using: .utf8) ?? Data()
case .long(let value):
return withUnsafeBytes(of: value.littleEndian) { Data($0) }
case .bytes(let value):
return value
case .date(let value):
let milliseconds = value.millisecondsTimeIntervalSince1970
return withUnsafeBytes(of: milliseconds.littleEndian) { Data($0) }
}
}
}
extension Primitive {
/**
* `toJSON` returns the JSON encoding of the value.
*
* TODOs: We need to consider the case where the value is
* a byte array and a date.
*/
func toJSON() -> String {
switch self.value {
case .null:
return "null"
case .boolean(let value):
return "\"\(value)\""
case .integer(let value):
return "\(value)"
case .double(let value):
return "\(value)"
case .string(let value):
return "\"\(value.escaped())\""
case .long(let value):
return "\(value)"
case .bytes(let value):
return "\(value)"
case .date(let value):
return "\(value.millisecondsTimeIntervalSince1970)"
}
}
/**
* `toSortedJSON` returns the sorted JSON encoding of the value.
*/
func toSortedJSON() -> String {
return self.toJSON()
}
/**
* `deepcopy` copies itself deeply.
*/
func deepcopy() -> CRDTElement {
let primitive = Primitive(value: self.value, createdAt: self.createdAt)
primitive.setMovedAt(self.movedAt)
return primitive
}
}
extension Date {
/**
* `millisecondsTimeIntervalSince1970` returns the number representing the milliseconds elapsed between 1 January 1970 00:00:00 UTC and the given date.
* It's simmilar getTime() in javascript
*/
var millisecondsTimeIntervalSince1970: Int64 {
Int64(floor(self.timeIntervalSince1970 * 1000))
}
var trimedLessThanMilliseconds: Date {
Date(timeIntervalSince1970: TimeInterval(Double(self.millisecondsTimeIntervalSince1970) / 1000))
}
}