OSDN Git Service

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