OSDN Git Service

コメントを追加
[kcd/KCD.git] / KCD / JSONMapper.swift
1 //
2 //  JSONMapper.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2017/02/22.
6 //  Copyright © 2017年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10 import SwiftyJSON
11
12 struct MappingConfiguration<T: NSManagedObject> {
13     
14     let entity: Entity<T>
15     let dataKeys: [String]
16     let primaryKeys: [String]
17     let editorStore: CoreDataAccessor
18     let ignoreKeys: [String]
19     
20     init(entity: Entity<T>,
21          dataKeys: [String] = ["api_data"],
22          primaryKeys: [String] = ["id"],
23          editorStore: CoreDataAccessor,
24          ignoreKeys: [String] = []) {
25         
26         self.entity = entity
27         self.dataKeys = dataKeys
28         self.primaryKeys = primaryKeys
29         self.editorStore = editorStore
30         self.ignoreKeys = ignoreKeys
31     }
32 }
33
34 protocol JSONMapper {
35     
36     associatedtype ObjectType: NSManagedObject
37     
38     init(_ apiResponse: APIResponse)
39     
40     var apiResponse: APIResponse { get }
41     var configuration: MappingConfiguration<ObjectType> { get }
42     
43     func registerElement(_ element: JSON, to object: ObjectType)
44     func commit()
45     func beginRegister(_ object: ObjectType)
46     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool
47     func finishOperating()
48 }
49
50 extension String {
51     // delete api_ prefix.
52     func keyByDeletingPrefix() -> String {
53         
54         if self.count < 5 { return self }
55         
56         return String(self[index(startIndex, offsetBy: 4)...])
57     }
58 }
59
60 extension JSONMapper {
61     
62     var data: JSON { return apiResponse.json[configuration.dataKeys] }
63     
64     private func isEqual(_ lhs: AnyObject?, _ rhs: AnyObject?) -> Bool {
65         
66         if lhs == nil, rhs == nil { return true }
67         if let lhs = lhs, let rhs = rhs { return lhs.isEqual(rhs) }
68         
69         return false
70     }
71     
72     func setValueIfNeeded(_ value: JSON, to object: ObjectType, forKey key: String) {
73         
74         var validValue = value.object as AnyObject?
75         do {
76             
77             try object.validateValue(&validValue, forKey: key)
78             
79         } catch {
80             
81             return
82         }
83         
84         let old = object.value(forKey: key)
85         if !isEqual(old as AnyObject?, validValue) {
86             
87             object.setValue(validValue, forKey: key)
88         }
89     }
90     
91     func registerElement(_ element: JSON, to object: ObjectType) {
92         
93         beginRegister(object)
94         element.forEach { (key, value) in
95             
96             if configuration.ignoreKeys.contains(key) { return }
97             if handleExtraValue(value, forKey: key, to: object) { return }
98             
99             switch value.type {
100             case .array:
101                 value.array?.enumerated().forEach {
102                     
103                     let newKey = "\(key)_\($0.offset)"
104                     setValueIfNeeded($0.element, to: object, forKey: newKey)
105                 }
106                 
107             case .dictionary:
108                 value.forEach { (subKey: String, subValue) in
109                     
110                     let newKey = "\(key)_D_\(subKey.keyByDeletingPrefix())"
111                     setValueIfNeeded(subValue, to: object, forKey: newKey)
112                 }
113             default:
114                 
115                 setValueIfNeeded(value, to: object, forKey: key)
116             }
117         }
118     }
119     
120     private var sortDescriptors: [NSSortDescriptor] {
121         
122         return configuration.primaryKeys.map { NSSortDescriptor(key: $0, ascending: true) }
123     }
124     
125     private func objectSearch(_ objects: [ObjectType], _ element: JSON) -> ObjectType? {
126         
127         let keyPiar = configuration.primaryKeys.map { (key: $0, apiKey: "api_\($0)") }
128         
129         return objects.binarySearch {
130             
131             // TODO: replace to forEach
132             for piar in keyPiar {
133                 
134                 guard let v1 = $0.value(forKey: piar.key) else { return .orderedAscending }
135                 
136                 if element[piar.apiKey].type == .null { return .orderedDescending }
137                 
138                 let v2 = element[piar.apiKey].object
139                 
140                 return (v1 as AnyObject).compare(v2)
141             }
142             
143             return .orderedDescending
144         }
145     }
146     
147     func commit() {
148         
149         let store = configuration.editorStore
150         
151         guard let objects = try? store.objects(of: configuration.entity, sortDescriptors: sortDescriptors) else {
152             
153             return Logger.shared.log("Can not get entity named \(configuration.entity.name)")
154         }
155         
156         let list = (data.type == .array ? data.arrayValue : [data])
157         list.forEach {
158             
159             if let object = objectSearch(objects, $0) {
160                 
161                 registerElement($0, to: object)
162                 
163             } else if let new = store.insertNewObject(for: configuration.entity) {
164                 
165                 registerElement($0, to: new)
166                 
167             } else {
168                 
169                 fatalError("Can not get entity named \(configuration.entity.name)")
170             }
171         }
172         
173         finishOperating()
174         store.save(errorHandler: store.presentOnMainThread)
175     }
176     
177     func beginRegister(_ object: ObjectType) {}
178     
179     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool {
180         
181         return false
182     }
183     
184     func finishOperating() {}
185 }