OSDN Git Service

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