OSDN Git Service

Entityをジェネリクスにし、それに伴いCoreData関連もジェネリクスにした
[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
11 struct MappingConfiguration {
12     let entityType: NSManagedObject.Type
13     let dataKey: String
14     let primaryKey: String
15     let compositPrimaryKeys: [String]?
16     let editorStore: CoreDataAccessor
17     let ignoreKeys: [String]
18     
19     init(entityType: NSManagedObject.Type,
20          dataKey: String = "api_data",
21          primaryKey: String = "id",
22          compositPrimaryKeys: [String]? = nil,
23          editorStore: CoreDataAccessor,
24          ignoreKeys: [String] = []) {
25         self.entityType = entityType
26         self.dataKey = dataKey
27         self.primaryKey = primaryKey
28         self.compositPrimaryKeys = compositPrimaryKeys
29         self.editorStore = editorStore
30         self.ignoreKeys = ignoreKeys
31     }
32 }
33
34 protocol JSONMapper {
35     init(_ apiResponse: APIResponse)
36     
37     var apiResponse: APIResponse { get }
38     var configuration: MappingConfiguration { get }
39     
40     func registerElement(_ element: [String: Any], to object: NSManagedObject)
41     func commit()
42     func beginRegister(_ object: NSManagedObject)
43     func handleExtraValue(_ value: Any, forKey key: String, to object: NSManagedObject) -> Bool
44     func finishOperating()
45 }
46
47 extension String {
48     func keyByDeletingPrefix() -> String {
49         if self.characters.count < 5 { return self }
50         let s = self.index(self.startIndex, offsetBy: 4)
51         return self[s..<self.endIndex]
52     }
53 }
54
55 extension JSONMapper {
56     private func isEqual(_ a: AnyObject?, _ b: AnyObject?) -> Bool {
57         if a == nil, b == nil { return true }
58         if let aa = a, let bb = b { return aa.isEqual(bb) }
59         return false
60     }
61     func setValueIfNeeded(_ value: AnyObject?, to object: NSManagedObject, forKey key: String) {
62         var validValue = value
63         do { try object.validateValue(&validValue, forKey: key) }
64         catch { return }
65         
66         let old = object.value(forKey: key)
67         if !isEqual(old as AnyObject?, validValue) {
68             object.willChangeValue(forKey: key)
69             object.setValue(validValue, forKey: key)
70             object.didChangeValue(forKey: key)
71         }
72     }
73     
74     func registerElement(_ element: [String: Any], to object: NSManagedObject) {
75         beginRegister(object)
76         element.forEach { (key: String, value: Any) in
77             if configuration.ignoreKeys.contains(key) { return }
78             if handleExtraValue(value, forKey: key, to: object) { return }
79             switch value {
80             case let a as [AnyObject]:
81                 a.enumerated().forEach {
82                     let newKey = "\(key)_\($0.offset)"
83                     setValueIfNeeded($0.element, to: object, forKey: newKey)
84                 }
85             case let d as [String: AnyObject]:
86                 d.forEach { (subKey: String, subValue) in
87                     let newKey = "\(key)_D_\(subKey.keyByDeletingPrefix())"
88                     setValueIfNeeded(subValue, to: object, forKey: newKey)
89                 }
90             default:
91                 setValueIfNeeded(value as AnyObject?, to: object, forKey: key)
92             }
93         }
94     }
95     private var sortDescriptors: [NSSortDescriptor] {
96         let keys = configuration.compositPrimaryKeys ?? [configuration.primaryKey]
97         return keys.map { NSSortDescriptor(key: $0, ascending: true) }
98     }
99     private func objectSearch(_ objects: [NSManagedObject], _ element: [String: Any]) -> NSManagedObject? {
100         let keys = configuration.compositPrimaryKeys ?? [configuration.primaryKey]
101         let keyPiar = keys.map { (key: $0, apiKey: "api_\($0)") }
102         return objects.binarySearch {
103             for piar in keyPiar {
104                 guard let v1 = $0.value(forKey: piar.key)
105                     else { return .orderedAscending }
106                 guard let v2 = element[piar.apiKey]
107                     else { return .orderedDescending }
108                 return (v1 as AnyObject).compare(v2)
109             }
110             return .orderedDescending
111         }
112     }
113     func commit() {
114         guard var d = (apiResponse.json as AnyObject).value(forKeyPath: configuration.dataKey)
115             else { return print("JSON is wrong.") }
116         if let dd = d as? NSDictionary { d = [dd] }
117         guard let data = d as? [[String: Any]]
118             else { return print("JSON is wrong.") }
119         
120         let store = configuration.editorStore
121         let entity = Entity(name: configuration.entityType.entityName,
122                             type: configuration.entityType)
123         guard let objects = try? store.objects(with: entity, sortDescriptors: sortDescriptors)
124             else { return print("Can not get entity named \(configuration.entityType.entityName)") }
125         data.forEach {
126             if let object = objectSearch(objects, $0) {
127                 registerElement($0, to: object)
128             } else if let new = store.insertNewObject(for: entity) {
129                 registerElement($0, to: new)
130             } else {
131                 fatalError("Can not create Entity")
132             }
133         }
134         finishOperating()
135         store.saveActionCore()
136     }
137     
138     func execute() {}
139     func beginRegister(_ object: NSManagedObject) {}
140     func handleExtraValue(_ value: Any, forKey key: String, to object: NSManagedObject) -> Bool {
141         return false
142     }
143     func finishOperating() {}
144 }
145
146
147