OSDN Git Service

ObjectTypeを明示せず推論させるようにした
[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.characters.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         
85         let old = object.value(forKey: key)
86         if !isEqual(old as AnyObject?, validValue) {
87             
88             object.notifyChangeValue(forKey: key) {
89                 
90                 object.setValue(validValue, forKey: key)
91                 
92             }
93         }
94     }
95     
96     func registerElement(_ element: JSON, to object: ObjectType) {
97         
98         beginRegister(object)
99         element.forEach { (key, value) in
100             
101             if configuration.ignoreKeys.contains(key) { return }
102             if handleExtraValue(value, forKey: key, to: object) { return }
103             
104             switch value.type {
105             case .array:
106                 value.array?.enumerated().forEach {
107                     
108                     let newKey = "\(key)_\($0.offset)"
109                     setValueIfNeeded($0.element, to: object, forKey: newKey)
110                 }
111                 
112             case .dictionary:
113                 value.forEach { (subKey: String, subValue) in
114                     
115                     let newKey = "\(key)_D_\(subKey.keyByDeletingPrefix())"
116                     setValueIfNeeded(subValue, to: object, forKey: newKey)
117                 }
118             default:
119                 
120                 setValueIfNeeded(value, to: object, forKey: key)
121             }
122         }
123     }
124     
125     private var sortDescriptors: [NSSortDescriptor] {
126         
127         return configuration.primaryKeys.map { NSSortDescriptor(key: $0, ascending: true) }
128     }
129     
130     private func objectSearch(_ objects: [ObjectType], _ element: JSON) -> ObjectType? {
131         
132         let keyPiar = configuration.primaryKeys.map { (key: $0, apiKey: "api_\($0)") }
133         
134         return objects.binarySearch {
135             
136             // TODO: replace to forEach
137             for piar in keyPiar {
138                 
139                 guard let v1 = $0.value(forKey: piar.key) else { return .orderedAscending }
140                 
141                 if element[piar.apiKey].type == .null { return .orderedDescending }
142                 
143                 let v2 = element[piar.apiKey].object
144                 
145                 return (v1 as AnyObject).compare(v2)
146             }
147             
148             return .orderedDescending
149         }
150     }
151     
152     func commit() {
153         
154         let store = configuration.editorStore
155         
156         guard let objects = try? store.objects(of: configuration.entity, sortDescriptors: sortDescriptors) else {
157             
158             print("Can not get entity named \(configuration.entity.name)")
159             return
160         }
161         
162         let list = (data.type == .array ? data.arrayValue : [data])
163         list.forEach {
164             
165             if let object = objectSearch(objects, $0) {
166                 
167                 registerElement($0, to: object)
168                 
169             } else if let new = store.insertNewObject(for: configuration.entity) {
170                 
171                 registerElement($0, to: new)
172                 
173             } else {
174                 
175                 fatalError("Can not get entity named \(configuration.entity.name)")
176                 
177             }
178         }
179         
180         finishOperating()
181         store.save()
182     }
183     
184     func beginRegister(_ object: ObjectType) {}
185     
186     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool {
187         
188         return false
189     }
190     
191     func finishOperating() {}
192 }