OSDN Git Service

MappingConfigurationのプロパティを変更
[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 primaryKey: String
16     let compositPrimaryKeys: [String]?
17     let editorStore: CoreDataAccessor
18     let ignoreKeys: [String]
19     
20     init(entity: Entity<T>,
21          dataKeys: [String] = ["api_data"],
22          primaryKey: String = "id",
23          compositPrimaryKeys: [String]? = nil,
24          editorStore: CoreDataAccessor,
25          ignoreKeys: [String] = []) {
26         self.entity = entity
27         self.dataKeys = dataKeys
28         self.primaryKey = primaryKey
29         self.compositPrimaryKeys = compositPrimaryKeys
30         self.editorStore = editorStore
31         self.ignoreKeys = ignoreKeys
32     }
33 }
34
35 protocol JSONMapper {
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     func keyByDeletingPrefix() -> String {
52         if self.characters.count < 5 { return self }
53         let s = self.index(self.startIndex, offsetBy: 4)
54         return self[s..<self.endIndex]
55     }
56 }
57
58 extension JSONMapper {
59     var data: JSON { return apiResponse.json[configuration.dataKeys] }
60     
61     private func isEqual(_ lhs: AnyObject?, _ rhs: AnyObject?) -> Bool {
62         if lhs == nil, rhs == nil { return true }
63         if let lhs = lhs, let rhs = rhs { return lhs.isEqual(rhs) }
64         return false
65     }
66     func setValueIfNeeded(_ value: JSON, to object: ObjectType, forKey key: String) {
67         var validValue = value.object as AnyObject?
68         do {
69             try object.validateValue(&validValue, forKey: key)
70         } catch {
71             return
72         }
73         
74         let old = object.value(forKey: key)
75         if !isEqual(old as AnyObject?, validValue) {
76             object.willChangeValue(forKey: key)
77             object.setValue(validValue, forKey: key)
78             object.didChangeValue(forKey: key)
79         }
80     }
81     
82     func registerElement(_ element: JSON, to object: ObjectType) {
83         beginRegister(object)
84         element.forEach { (key: String, value: JSON) in
85             if configuration.ignoreKeys.contains(key) { return }
86             if handleExtraValue(value, forKey: key, to: object) { return }
87             switch value.type {
88             case .array:
89                 value.array?.enumerated().forEach {
90                     let newKey = "\(key)_\($0.offset)"
91                     setValueIfNeeded($0.element, to: object, forKey: newKey)
92                 }
93             case .dictionary:
94                 value.forEach { (subKey: String, subValue) in
95                     let newKey = "\(key)_D_\(subKey.keyByDeletingPrefix())"
96                     setValueIfNeeded(subValue, to: object, forKey: newKey)
97                 }
98             default:
99                 setValueIfNeeded(value, to: object, forKey: key)
100             }
101         }
102     }
103     private var sortDescriptors: [NSSortDescriptor] {
104         let keys = configuration.compositPrimaryKeys ?? [configuration.primaryKey]
105         return keys.map { NSSortDescriptor(key: $0, ascending: true) }
106     }
107     private func objectSearch(_ objects: [ObjectType], _ element: JSON) -> ObjectType? {
108         let keys = configuration.compositPrimaryKeys ?? [configuration.primaryKey]
109         let keyPiar = keys.map { (key: $0, apiKey: "api_\($0)") }
110         return objects.binarySearch {
111             for piar in keyPiar {
112                 guard let v1 = $0.value(forKey: piar.key)
113                     else { return .orderedAscending }
114                 if element[piar.apiKey].type == .null { return .orderedDescending }
115                 let v2 = element[piar.apiKey].object
116                 return (v1 as AnyObject).compare(v2)
117             }
118             return .orderedDescending
119         }
120     }
121     func commit() {
122         let store = configuration.editorStore
123         guard let objects = try? store.objects(with: configuration.entity, sortDescriptors: sortDescriptors)
124             else { return print("Can not get entity named \(configuration.entity.name)") }
125         let list = (data.type == .array ? data.arrayValue : [data])
126         list.forEach {
127             if let object = objectSearch(objects, $0) {
128                 registerElement($0, to: object)
129             } else if let new = store.insertNewObject(for: configuration.entity) {
130                 registerElement($0, to: new)
131             } else {
132                 fatalError("Can not get entity named \(configuration.entity.name)")
133             }
134         }
135         finishOperating()
136         store.saveActionCore()
137     }
138     
139     func beginRegister(_ object: ObjectType) {}
140     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool {
141         return false
142     }
143     func finishOperating() {}
144 }