OSDN Git Service

AppDelegateからウインドウに関する部分を分離した
[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     let entity: Entity<T>
14     let dataKeys: [String]
15     let primaryKeys: [String]
16     let editorStore: CoreDataAccessor
17     let ignoreKeys: [String]
18     
19     init(entity: Entity<T>,
20          dataKeys: [String] = ["api_data"],
21          primaryKeys: [String] = ["id"],
22          editorStore: CoreDataAccessor,
23          ignoreKeys: [String] = []) {
24         self.entity = entity
25         self.dataKeys = dataKeys
26         self.primaryKeys = primaryKeys
27         self.editorStore = editorStore
28         self.ignoreKeys = ignoreKeys
29     }
30 }
31
32 protocol JSONMapper {
33     associatedtype ObjectType: NSManagedObject
34     
35     init(_ apiResponse: APIResponse)
36     
37     var apiResponse: APIResponse { get }
38     var configuration: MappingConfiguration<ObjectType> { get }
39     
40     func registerElement(_ element: JSON, to object: ObjectType)
41     func commit()
42     func beginRegister(_ object: ObjectType)
43     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> 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     var data: JSON { return apiResponse.json[configuration.dataKeys] }
57     
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: JSON, to object: ObjectType, forKey key: String) {
64         var validValue = value.object as AnyObject?
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.notifyChangeValue(forKey: key) {
74                 object.setValue(validValue, forKey: key)
75             }
76         }
77     }
78     
79     func registerElement(_ element: JSON, to object: ObjectType) {
80         beginRegister(object)
81         element.forEach { (key, value) in
82             if configuration.ignoreKeys.contains(key) { return }
83             if handleExtraValue(value, forKey: key, to: object) { return }
84             switch value.type {
85             case .array:
86                 value.array?.enumerated().forEach {
87                     let newKey = "\(key)_\($0.offset)"
88                     setValueIfNeeded($0.element, to: object, forKey: newKey)
89                 }
90             case .dictionary:
91                 value.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, to: object, forKey: key)
97             }
98         }
99     }
100     private var sortDescriptors: [NSSortDescriptor] {
101         return configuration.primaryKeys.map { NSSortDescriptor(key: $0, ascending: true) }
102     }
103     private func objectSearch(_ objects: [ObjectType], _ element: JSON) -> ObjectType? {
104         let keyPiar = configuration.primaryKeys.map { (key: $0, apiKey: "api_\($0)") }
105         return objects.binarySearch {
106             for piar in keyPiar {
107                 guard let v1 = $0.value(forKey: piar.key)
108                     else { return .orderedAscending }
109                 if element[piar.apiKey].type == .null { return .orderedDescending }
110                 let v2 = element[piar.apiKey].object
111                 return (v1 as AnyObject).compare(v2)
112             }
113             return .orderedDescending
114         }
115     }
116     func commit() {
117         let store = configuration.editorStore
118         guard let objects = try? store.objects(with: configuration.entity, sortDescriptors: sortDescriptors)
119             else { return print("Can not get entity named \(configuration.entity.name)") }
120         let list = (data.type == .array ? data.arrayValue : [data])
121         list.forEach {
122             if let object = objectSearch(objects, $0) {
123                 registerElement($0, to: object)
124             } else if let new = store.insertNewObject(for: configuration.entity) {
125                 registerElement($0, to: new)
126             } else {
127                 fatalError("Can not get entity named \(configuration.entity.name)")
128             }
129         }
130         finishOperating()
131         store.save()
132     }
133     
134     func beginRegister(_ object: ObjectType) {}
135     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool {
136         return false
137     }
138     func finishOperating() {}
139 }