OSDN Git Service

Doutaku を 1.0 にアップデート
[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 import Doutaku
12
13 struct MappingConfiguration<T: Entity> {
14     
15     let entity: T.Type
16     let dataKeys: [String]
17     let primaryKeys: [String]
18     let editorStore: CoreDataAccessor
19     let ignoreKeys: [String]
20     
21     init(entity: T.Type,
22          dataKeys: [String] = ["api_data"],
23          primaryKeys: [String] = ["id"],
24          editorStore: CoreDataAccessor,
25          ignoreKeys: [String] = []) {
26         
27         self.entity = entity
28         self.dataKeys = dataKeys
29         self.primaryKeys = primaryKeys
30         self.editorStore = editorStore
31         self.ignoreKeys = ignoreKeys
32     }
33 }
34
35 protocol JSONMapper {
36     
37     associatedtype ObjectType: Entity
38     
39     init(_ apiResponse: APIResponse)
40     
41     var apiResponse: APIResponse { get }
42     var configuration: MappingConfiguration<ObjectType> { get }
43     
44     func registerElement(_ element: JSON, to object: ObjectType)
45     func commit()
46     func beginRegister(_ object: ObjectType)
47     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool
48     func finishOperating()
49 }
50
51 extension String {
52     // delete api_ prefix.
53     func keyByDeletingPrefix() -> String {
54         
55         if self.count < 5 {
56             
57             return self
58         }
59         
60         return String(self[index(startIndex, offsetBy: 4)...])
61     }
62 }
63
64 extension JSONMapper {
65     
66     var data: JSON { return apiResponse.json[configuration.dataKeys] }
67     
68     private func isEqual(_ lhs: AnyObject?, _ rhs: AnyObject?) -> Bool {
69         
70         if lhs == nil, rhs == nil {
71             
72             return true
73         }
74         if let lhs = lhs, let rhs = rhs {
75             
76             return lhs.isEqual(rhs)
77         }
78         
79         return false
80     }
81     
82     func setValueIfNeeded(_ value: JSON, to object: ObjectType, forKey key: String) {
83         
84         var validValue = value.object as AnyObject?
85         do {
86             
87             try object.validateValue(&validValue, forKey: key)
88             
89         } catch {
90             
91             return
92         }
93         
94         let old = object.value(forKey: key)
95         if !isEqual(old as AnyObject?, validValue) {
96             
97             object.setValue(validValue, forKey: key)
98         }
99     }
100     
101     func registerElement(_ element: JSON, to object: ObjectType) {
102         
103         beginRegister(object)
104         element.forEach { (key, value) in
105             
106             if configuration.ignoreKeys.contains(key) {
107                 
108                 return
109             }
110             if handleExtraValue(value, forKey: key, to: object) {
111                 
112                 return
113             }
114             
115             switch value.type {
116             case .array:
117                 value.array?.enumerated().forEach {
118                     
119                     let newKey = "\(key)_\($0.offset)"
120                     setValueIfNeeded($0.element, to: object, forKey: newKey)
121                 }
122                 
123             case .dictionary:
124                 value.forEach { (subKey: String, subValue) in
125                     
126                     let newKey = "\(key)_D_\(subKey.keyByDeletingPrefix())"
127                     setValueIfNeeded(subValue, to: object, forKey: newKey)
128                 }
129                 
130             default:
131                 setValueIfNeeded(value, to: object, forKey: key)
132                 
133             }
134         }
135     }
136     
137     private var sortDescriptors: [NSSortDescriptor] {
138         
139         return configuration.primaryKeys.map { NSSortDescriptor(key: $0, ascending: true) }
140     }
141     
142     private func objectSearch(_ objects: [ObjectType], _ element: JSON) -> ObjectType? {
143         
144         let keyPiar = configuration.primaryKeys.map { (key: $0, apiKey: "api_\($0)") }
145         
146         return objects.binarySearch {
147             
148             // TODO: replace to forEach
149             for piar in keyPiar {
150                 
151                 guard let v1 = $0.value(forKey: piar.key) else {
152                     
153                     return .orderedAscending
154                 }
155                 
156                 if element[piar.apiKey].type == .null {
157                     
158                     return .orderedDescending
159                 }
160                 
161                 let v2 = element[piar.apiKey].object
162                 
163                 return (v1 as AnyObject).compare(v2)
164             }
165             
166             return .orderedDescending
167         }
168     }
169     
170     private func sortedObjects<ResultType: Entity>(_ entity: ResultType.Type) -> [ResultType] {
171         
172         let store = configuration.editorStore
173         
174         guard let objects = try? store.objects(of: configuration.entity) else {
175             
176             Logger.shared.log("Can not get entity named \(configuration.entity)")
177             
178             return []
179         }
180         
181         return (objects as NSArray).sortedArray(using: sortDescriptors) as? [ResultType] ?? []
182     }
183     private func commintInContext() {
184         
185         let store = configuration.editorStore
186         let objects = sortedObjects(configuration.entity)
187         
188         let list = (data.type == .array ? data.arrayValue : [data])
189         list.forEach {
190             
191             if let object = objectSearch(objects, $0) {
192                 
193                 registerElement($0, to: object)
194                 
195             } else if let new = store.insertNewObject(for: configuration.entity) {
196                 
197                 registerElement($0, to: new)
198                 
199             } else {
200                 
201                 fatalError("Can not get entity named \(configuration.entity)")
202             }
203         }
204         
205         finishOperating()
206         store.save()
207     }
208     
209     func commit() {
210         
211         configuration.editorStore
212             .sync {
213                 
214                 self.commintInContext()
215         }
216     }
217     
218     func beginRegister(_ object: ObjectType) {}
219     
220     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool {
221         
222         return false
223     }
224     
225     func finishOperating() {}
226 }