FirestoreEncoder.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. // This file is derived from swift/stdlib/public/SDK/Foundation/JSONEncoder.swift
  2. // and swift/stdlib/public/SDK/Foundation/PlistEncoder.swift
  3. //===----------------------------------------------------------------------===//
  4. //
  5. // This source file is part of the Swift.org open source project
  6. //
  7. // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
  8. // Licensed under Apache License v2.0 with Runtime Library Exception
  9. //
  10. // See https://swift.org/LICENSE.txt for license information
  11. // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
  12. //
  13. //===----------------------------------------------------------------------===//
  14. import FirebaseFirestore
  15. import Foundation
  16. extension Firestore {
  17. public struct Encoder {
  18. public init() {}
  19. /// Returns encoded data that Firestore API recognizes.
  20. ///
  21. /// If possible, all types will be converted to compatible types Firestore
  22. /// can handle. This means certain Firestore specific types will be encoded
  23. /// as pass-through: this encoder will only pass those types along since that
  24. /// is what Firestore can handle. The same types will be encoded differently
  25. /// with other encoders (for example: JSONEncoder).
  26. ///
  27. /// The Firestore pass-through types are:
  28. /// - GeoPoint
  29. /// - Timestamp
  30. /// - DocumentReference
  31. ///
  32. /// - Parameter value: The Encodable object to convert to encoded data.
  33. /// - Returns: A Map keyed by String representing a document Firestore
  34. /// API can work with.
  35. public func encode<T: Encodable>(_ value: T) throws -> [String: Any] {
  36. // SelfDocumentID, DocumentReference and FieldValue cannot be
  37. // encoded directly.
  38. guard T.self != DocumentReference.self,
  39. T.self != FieldValue.self else {
  40. throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) is not allowed."))
  41. }
  42. guard let topLevel = try _FirestoreEncoder().box_(value) else {
  43. throw EncodingError.invalidValue(value,
  44. EncodingError.Context(codingPath: [],
  45. debugDescription: "Top-level \(T.self) did not encode any values."))
  46. }
  47. // This is O(n) check. Consider refactoring box_ to return [String: Any].
  48. guard let dict = topLevel as? [String: Any] else {
  49. throw EncodingError.invalidValue(value,
  50. EncodingError.Context(codingPath: [],
  51. debugDescription: "Top-level \(T.self) encoded not as dictionary."))
  52. }
  53. return dict
  54. }
  55. }
  56. }
  57. private class _FirestoreEncoder: Encoder {
  58. /// A stack of data containers storing encoded results. When a new object is being encoded,
  59. /// a corresponding storage is pushed to the stack; and when the field and all of its children objects
  60. /// are encoded, the stoage should have the entire encoded result for the sub-tree, it is then poped
  61. /// out and written to the proper place of the new stack top container by referencing to the top of `codingPath`.
  62. fileprivate var storage: _FirestoreEncodingStorage
  63. /// An array used as a stack to keep track of where in the encoded data tree the encoder is trying to process
  64. /// at the moment.
  65. public fileprivate(set) var codingPath: [CodingKey]
  66. public var userInfo: [CodingUserInfoKey: Any] = [:]
  67. init() {
  68. storage = _FirestoreEncodingStorage()
  69. codingPath = []
  70. }
  71. /// Returns whether we should allocate new container to store encoded result for the current
  72. /// codingPath.
  73. ///
  74. /// `true` if no container has been allocated for this coding path; `false` otherwise.
  75. fileprivate var shouldAllocateNewContainer: Bool {
  76. // The encoder starts with storage.count == codingPath.count == 0, which means it is encoding the root
  77. // object without any container allocated, so when a container is requested, it must be allocated
  78. // first. When a container is requested again with the same codingPath however, for another field from the root object possibly,
  79. // because now storage.count == codingPath + 1, we should not allocate new containers because there are
  80. // already encoded results in the container. This relation between the two stacks is maintained
  81. // throughout the encoding process.
  82. return storage.count == codingPath.count
  83. }
  84. // MARK: - Encoder Methods
  85. public func container<Key>(keyedBy _: Key.Type) -> KeyedEncodingContainer<Key> {
  86. // If an existing keyed container was already requested, return that one.
  87. let topContainer: NSMutableDictionary
  88. if shouldAllocateNewContainer {
  89. // We haven't yet pushed a container at this level; do so here.
  90. topContainer = storage.pushKeyedContainer()
  91. } else {
  92. guard let container = storage.containers.last as? NSMutableDictionary else {
  93. preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.")
  94. }
  95. topContainer = container
  96. }
  97. let container = _FirestoreKeyedEncodingContainer<Key>(referencing: self, codingPath: codingPath, wrapping: topContainer)
  98. return KeyedEncodingContainer(container)
  99. }
  100. public func unkeyedContainer() -> UnkeyedEncodingContainer {
  101. // If an existing unkeyed container was already requested, return that one.
  102. let topContainer: NSMutableArray
  103. if shouldAllocateNewContainer {
  104. // We haven't yet pushed a container at this level; do so here.
  105. topContainer = storage.pushUnkeyedContainer()
  106. } else {
  107. guard let container = storage.containers.last as? NSMutableArray else {
  108. preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.")
  109. }
  110. topContainer = container
  111. }
  112. return _FirestoreUnkeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer)
  113. }
  114. public func singleValueContainer() -> SingleValueEncodingContainer {
  115. return self
  116. }
  117. }
  118. private struct _FirestoreEncodingStorage {
  119. // MARK: Properties
  120. /// The container stack.
  121. /// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary).
  122. fileprivate private(set) var containers: [NSObject] = []
  123. // MARK: - Initialization
  124. /// Initializes `self` with no containers.
  125. fileprivate init() {}
  126. // MARK: - Modifying the Stack
  127. fileprivate var count: Int {
  128. return containers.count
  129. }
  130. fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary {
  131. let dictionary = NSMutableDictionary()
  132. containers.append(dictionary)
  133. return dictionary
  134. }
  135. fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray {
  136. let array = NSMutableArray()
  137. containers.append(array)
  138. return array
  139. }
  140. fileprivate mutating func push(container: NSObject) {
  141. containers.append(container)
  142. }
  143. fileprivate mutating func popContainer() -> NSObject {
  144. precondition(containers.count > 0, "Empty container stack.")
  145. let ret = containers.popLast()!
  146. return ret
  147. }
  148. }
  149. private struct _FirestoreKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol {
  150. typealias Key = K
  151. // MARK: Properties
  152. /// A reference to the encoder we're writing to.
  153. private let encoder: _FirestoreEncoder
  154. /// A reference to the container we're writing to.
  155. private let container: NSMutableDictionary
  156. /// The path of coding keys taken to get to this point in encoding.
  157. public private(set) var codingPath: [CodingKey]
  158. // MARK: - Initialization
  159. /// Initializes `self` with the given references.
  160. fileprivate init(referencing encoder: _FirestoreEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) {
  161. self.encoder = encoder
  162. self.codingPath = codingPath
  163. self.container = container
  164. }
  165. // MARK: - KeyedEncodingContainerProtocol Methods
  166. public mutating func encodeNil(forKey key: Key) throws { container[key.stringValue] = NSNull() }
  167. public mutating func encode(_ value: Bool, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  168. public mutating func encode(_ value: Int, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  169. public mutating func encode(_ value: Int8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  170. public mutating func encode(_ value: Int16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  171. public mutating func encode(_ value: Int32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  172. public mutating func encode(_ value: Int64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  173. public mutating func encode(_ value: UInt, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  174. public mutating func encode(_ value: UInt8, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  175. public mutating func encode(_ value: UInt16, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  176. public mutating func encode(_ value: UInt32, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  177. public mutating func encode(_ value: UInt64, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  178. public mutating func encode(_ value: String, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  179. public mutating func encode(_ value: Float, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  180. public mutating func encode(_ value: Double, forKey key: Key) throws { container[key.stringValue] = encoder.box(value) }
  181. public mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
  182. #if compiler(>=5.1)
  183. // `DocumentID`-annotated fields are ignored during encoding.
  184. if T.self is DocumentIDProtocol.Type {
  185. return
  186. }
  187. #endif // compiler(>=5.1)
  188. encoder.codingPath.append(key)
  189. defer {
  190. encoder.codingPath.removeLast()
  191. }
  192. container[key.stringValue] = try encoder.box(value)
  193. }
  194. public mutating func nestedContainer<NestedKey>(keyedBy _: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
  195. let dictionary = NSMutableDictionary()
  196. self.container[key.stringValue] = dictionary
  197. codingPath.append(key)
  198. defer {
  199. codingPath.removeLast()
  200. }
  201. let container = _FirestoreKeyedEncodingContainer<NestedKey>(referencing: encoder, codingPath: codingPath, wrapping: dictionary)
  202. return KeyedEncodingContainer(container)
  203. }
  204. public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
  205. let array = NSMutableArray()
  206. container[key.stringValue] = array
  207. codingPath.append(key)
  208. defer {
  209. codingPath.removeLast()
  210. }
  211. return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array)
  212. }
  213. public mutating func superEncoder() -> Encoder {
  214. return _FirestoreReferencingEncoder(referencing: encoder, at: _FirestoreKey.super, wrapping: container)
  215. }
  216. public mutating func superEncoder(forKey key: Key) -> Encoder {
  217. return _FirestoreReferencingEncoder(referencing: encoder, at: key, wrapping: container)
  218. }
  219. }
  220. private struct _FirestoreUnkeyedEncodingContainer: UnkeyedEncodingContainer {
  221. // MARK: Properties
  222. /// A reference to the encoder we're writing to.
  223. private let encoder: _FirestoreEncoder
  224. /// A reference to the container we're writing to.
  225. private let container: NSMutableArray
  226. /// The path of coding keys taken to get to this point in encoding.
  227. public private(set) var codingPath: [CodingKey]
  228. /// The number of elements encoded into the container.
  229. public var count: Int {
  230. return container.count
  231. }
  232. // MARK: - Initialization
  233. /// Initializes `self` with the given references.
  234. fileprivate init(referencing encoder: _FirestoreEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) {
  235. self.encoder = encoder
  236. self.codingPath = codingPath
  237. self.container = container
  238. }
  239. // MARK: - UnkeyedEncodingContainer Methods
  240. public mutating func encodeNil() throws { container.add(NSNull()) }
  241. public mutating func encode(_ value: Bool) throws { container.add(encoder.box(value)) }
  242. public mutating func encode(_ value: Int) throws { container.add(encoder.box(value)) }
  243. public mutating func encode(_ value: Int8) throws { container.add(encoder.box(value)) }
  244. public mutating func encode(_ value: Int16) throws { container.add(encoder.box(value)) }
  245. public mutating func encode(_ value: Int32) throws { container.add(encoder.box(value)) }
  246. public mutating func encode(_ value: Int64) throws { container.add(encoder.box(value)) }
  247. public mutating func encode(_ value: UInt) throws { container.add(encoder.box(value)) }
  248. public mutating func encode(_ value: UInt8) throws { container.add(encoder.box(value)) }
  249. public mutating func encode(_ value: UInt16) throws { container.add(encoder.box(value)) }
  250. public mutating func encode(_ value: UInt32) throws { container.add(encoder.box(value)) }
  251. public mutating func encode(_ value: UInt64) throws { container.add(encoder.box(value)) }
  252. public mutating func encode(_ value: Float) throws { container.add(encoder.box(value)) }
  253. public mutating func encode(_ value: Double) throws { container.add(encoder.box(value)) }
  254. public mutating func encode(_ value: String) throws { container.add(encoder.box(value)) }
  255. public mutating func encode<T: Encodable>(_ value: T) throws {
  256. encoder.codingPath.append(_FirestoreKey(index: count))
  257. defer { encoder.codingPath.removeLast()
  258. }
  259. container.add(try encoder.box(value))
  260. }
  261. public mutating func nestedContainer<NestedKey>(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
  262. codingPath.append(_FirestoreKey(index: count))
  263. defer {
  264. self.codingPath.removeLast()
  265. }
  266. let dictionary = NSMutableDictionary()
  267. self.container.add(dictionary)
  268. let container = _FirestoreKeyedEncodingContainer<NestedKey>(referencing: encoder, codingPath: codingPath, wrapping: dictionary)
  269. return KeyedEncodingContainer(container)
  270. }
  271. public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
  272. codingPath.append(_FirestoreKey(index: count))
  273. defer {
  274. self.codingPath.removeLast()
  275. }
  276. let array = NSMutableArray()
  277. container.add(array)
  278. return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath, wrapping: array)
  279. }
  280. public mutating func superEncoder() -> Encoder {
  281. return _FirestoreReferencingEncoder(referencing: encoder, at: container.count, wrapping: container)
  282. }
  283. }
  284. struct _FirestoreKey: CodingKey {
  285. public var stringValue: String
  286. public var intValue: Int?
  287. public init?(stringValue: String) {
  288. self.stringValue = stringValue
  289. intValue = nil
  290. }
  291. public init?(intValue: Int) {
  292. stringValue = "\(intValue)"
  293. self.intValue = intValue
  294. }
  295. init(index: Int) {
  296. stringValue = "Index \(index)"
  297. intValue = index
  298. }
  299. static let `super` = _FirestoreKey(stringValue: "super")!
  300. }
  301. extension _FirestoreEncoder {
  302. /// Returns the given value boxed in a container appropriate for pushing onto the container stack.
  303. fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) }
  304. fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) }
  305. fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) }
  306. fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) }
  307. fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) }
  308. fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) }
  309. fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) }
  310. fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) }
  311. fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) }
  312. fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) }
  313. fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) }
  314. fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) }
  315. fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) }
  316. fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) }
  317. fileprivate func box<T: Encodable>(_ value: T) throws -> NSObject {
  318. return try box_(value) ?? NSDictionary()
  319. }
  320. func box_<T: Encodable>(_ value: T) throws -> NSObject? {
  321. if T.self == Date.self || T.self == NSDate.self {
  322. return (value as! NSDate)
  323. } else if T.self == Data.self || T.self == NSData.self {
  324. return (value as! NSData)
  325. } else if T.self == URL.self || T.self == NSURL.self {
  326. return box((value as! URL).absoluteString)
  327. } else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
  328. return (value as! NSDecimalNumber)
  329. } else if isFirestorePassthroughType(value) {
  330. // This is a native Firestore types that we don't need to encode.
  331. return (value as! NSObject)
  332. }
  333. // The value should request a container from the _FirestoreEncoder.
  334. let depth = storage.count
  335. do {
  336. try value.encode(to: self)
  337. } catch {
  338. // If the value pushed a container before throwing, pop it back off to restore state.
  339. if storage.count > depth {
  340. _ = storage.popContainer()
  341. }
  342. throw error
  343. }
  344. // The top container should be a new container.
  345. guard storage.count > depth else {
  346. return nil
  347. }
  348. return storage.popContainer()
  349. }
  350. }
  351. extension _FirestoreEncoder: SingleValueEncodingContainer {
  352. // MARK: - SingleValueEncodingContainer Methods
  353. private func assertCanEncodeNewValue() {
  354. precondition(shouldAllocateNewContainer, "Attempt to encode value through single value container when previously value already encoded.")
  355. }
  356. public func encodeNil() throws {
  357. assertCanEncodeNewValue()
  358. storage.push(container: NSNull())
  359. }
  360. public func encode(_ value: Bool) throws {
  361. assertCanEncodeNewValue()
  362. storage.push(container: box(value))
  363. }
  364. public func encode(_ value: Int) throws {
  365. assertCanEncodeNewValue()
  366. storage.push(container: box(value))
  367. }
  368. public func encode(_ value: Int8) throws {
  369. assertCanEncodeNewValue()
  370. storage.push(container: box(value))
  371. }
  372. public func encode(_ value: Int16) throws {
  373. assertCanEncodeNewValue()
  374. storage.push(container: box(value))
  375. }
  376. public func encode(_ value: Int32) throws {
  377. assertCanEncodeNewValue()
  378. storage.push(container: box(value))
  379. }
  380. public func encode(_ value: Int64) throws {
  381. assertCanEncodeNewValue()
  382. storage.push(container: box(value))
  383. }
  384. public func encode(_ value: UInt) throws {
  385. assertCanEncodeNewValue()
  386. storage.push(container: box(value))
  387. }
  388. public func encode(_ value: UInt8) throws {
  389. assertCanEncodeNewValue()
  390. storage.push(container: box(value))
  391. }
  392. public func encode(_ value: UInt16) throws {
  393. assertCanEncodeNewValue()
  394. storage.push(container: box(value))
  395. }
  396. public func encode(_ value: UInt32) throws {
  397. assertCanEncodeNewValue()
  398. storage.push(container: box(value))
  399. }
  400. public func encode(_ value: UInt64) throws {
  401. assertCanEncodeNewValue()
  402. storage.push(container: box(value))
  403. }
  404. public func encode(_ value: String) throws {
  405. assertCanEncodeNewValue()
  406. storage.push(container: box(value))
  407. }
  408. public func encode(_ value: Float) throws {
  409. assertCanEncodeNewValue()
  410. storage.push(container: box(value))
  411. }
  412. public func encode(_ value: Double) throws {
  413. assertCanEncodeNewValue()
  414. storage.push(container: box(value))
  415. }
  416. public func encode<T: Encodable>(_ value: T) throws {
  417. assertCanEncodeNewValue()
  418. try storage.push(container: box(value))
  419. }
  420. }
  421. /// Special subclass of `_FirestoreEncoder` used by `superEncoder`.
  422. /// It inherits the codingPath from the referencing `_FirestoreEncoder` but uses its own
  423. /// storage. The encoded result will be written back to the referencing encoder's storage
  424. /// when it is `deinit`-ed.
  425. private class _FirestoreReferencingEncoder: _FirestoreEncoder {
  426. // MARK: Reference types.
  427. /// The type of container we're referencing, and where to write the encoded result to.
  428. private enum Reference {
  429. /// Referencing a specific index in an array container.
  430. case array(NSMutableArray, Int)
  431. /// Referencing a specific key in a dictionary container.
  432. case dictionary(NSMutableDictionary, String)
  433. }
  434. // MARK: - Properties
  435. /// The encoder we're referencing.
  436. private let encoder: _FirestoreEncoder
  437. /// The container reference itself.
  438. private let reference: Reference
  439. // MARK: - Initialization
  440. /// Initializes `self` by referencing the given array container in the given encoder.
  441. fileprivate init(referencing encoder: _FirestoreEncoder, at index: Int, wrapping array: NSMutableArray) {
  442. self.encoder = encoder
  443. reference = .array(array, index)
  444. super.init()
  445. codingPath = encoder.codingPath
  446. codingPath.append(_FirestoreKey(index: index))
  447. }
  448. /// Initializes `self` by referencing the given dictionary container in the given encoder.
  449. fileprivate init(referencing encoder: _FirestoreEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) {
  450. self.encoder = encoder
  451. reference = .dictionary(dictionary, key.stringValue)
  452. super.init()
  453. codingPath = encoder.codingPath
  454. codingPath.append(key)
  455. }
  456. // MARK: - Coding Path Operations
  457. fileprivate override var shouldAllocateNewContainer: Bool {
  458. // With a regular encoder, the storage and coding path grow together.
  459. // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for.
  460. // We have to take this into account.
  461. return storage.count == codingPath.count - encoder.codingPath.count - 1
  462. }
  463. // MARK: - Deinitialization
  464. // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage.
  465. deinit {
  466. let value: Any
  467. switch storage.count {
  468. case 0: value = NSDictionary()
  469. case 1: value = self.storage.popContainer()
  470. default: fatalError("Referencing encoder deallocated with multiple containers on stack.")
  471. }
  472. switch self.reference {
  473. case let .array(array, index):
  474. array.insert(value, at: index)
  475. case let .dictionary(dictionary, key):
  476. dictionary[NSString(string: key)] = value
  477. }
  478. }
  479. }