Split NJMappingController view handling off into NJMappingViewController. This is...
[enjoyable.git] / Classes / NJMapping.m
1 //
2 // NJMapping.m
3 // Enjoy
4 //
5 // Created by Sam McCall on 4/05/09.
6 //
7
8 #import "NJMapping.h"
9
10 #import "NJInput.h"
11 #import "NJOutput.h"
12
13 @implementation NJMapping {
14 NSMutableDictionary *_entries;
15 }
16
17 // Extra checks during initialization because the data is often loaded
18 // from untrusted serializations.
19
20 - (id)init {
21 if ((self = [super init])) {
22 self.name = NSLocalizedString(@"Untitled", @"name for new mappings");
23 _entries = [[NSMutableDictionary alloc] init];
24 }
25 return self;
26 }
27
28 - (id)initWithName:(NSString *)name {
29 if ((self = [self init])) {
30 if ([name isKindOfClass:NSString.class])
31 self.name = name;
32 }
33 return self;
34 }
35
36 - (id)initWithSerialization:(NSDictionary *)serialization
37 mappings:(id <NSFastEnumeration>)mappings {
38 if ((self = [self initWithName:serialization[@"name"]])) {
39 NSDictionary *entries = serialization[@"entries"];
40 if ([entries isKindOfClass:NSDictionary.class]) {
41 for (id key in entries) {
42 if ([key isKindOfClass:NSString.class]) {
43 NJOutput *output = [NJOutput outputDeserialize:entries[key]
44 withMappings:mappings];
45 if (output)
46 _entries[key] = output;
47 }
48 }
49 }
50 }
51 return self;
52 }
53
54 - (NJOutput *)objectForKeyedSubscript:(NJInput *)input {
55 return input ? _entries[input.uid] : nil;
56 }
57
58 - (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input {
59 if (input) {
60 if (output)
61 _entries[input.uid] = output;
62 else
63 [_entries removeObjectForKey:input.uid];
64 }
65 }
66
67 - (NSDictionary *)serialize {
68 NSMutableDictionary *entries = [[NSMutableDictionary alloc] initWithCapacity:_entries.count];
69 for (id key in _entries) {
70 id serialized = [_entries[key] serialize];
71 if (serialized)
72 entries[key] = serialized;
73 }
74 return @{ @"name": _name, @"entries": entries };
75 }
76
77 - (BOOL)writeToURL:(NSURL *)url error:(NSError **)error {
78 [NSProcessInfo.processInfo disableSuddenTermination];
79 NSDictionary *serialization = [self serialize];
80 NSData *json = [NSJSONSerialization dataWithJSONObject:serialization
81 options:NSJSONWritingPrettyPrinted
82 error:error];
83 BOOL success = json && [json writeToURL:url options:NSDataWritingAtomic error:error];
84 [NSProcessInfo.processInfo enableSuddenTermination];
85 return success;
86 }
87
88 - (NSUInteger)count {
89 return _entries.count;
90 }
91
92 - (BOOL)hasConflictWith:(NJMapping *)other {
93 if (other.count < self.count)
94 return [other hasConflictWith:self];
95 for (NSString *uid in _entries) {
96 NJOutput *output = other->_entries[uid];
97 if (output && ![output isEqual:_entries[uid]])
98 return YES;
99 }
100 return NO;
101 }
102
103 + (id)mappingWithContentsOfURL:(NSURL *)url mappings:(id <NSFastEnumeration>)mappings error:(NSError **)error {
104 NSInputStream *stream = [NSInputStream inputStreamWithURL:url];
105 [stream open];
106 NSDictionary *serialization = stream && !*error
107 ? [NSJSONSerialization JSONObjectWithStream:stream options:0 error:error]
108 : nil;
109 [stream close];
110
111 if (!serialization && error)
112 return nil;
113
114 if (!([serialization isKindOfClass:NSDictionary.class]
115 && [serialization[@"name"] isKindOfClass:NSString.class]
116 && [serialization[@"entries"] isKindOfClass:NSDictionary.class])) {
117 *error = [NSError errorWithDomain:@"Enjoyable"
118 code:0
119 description:NSLocalizedString(@"invalid mapping file",
120 @"error when imported file was JSON but not a mapping")];
121 return nil;
122 }
123
124 return [[NJMapping alloc] initWithSerialization:serialization
125 mappings:mappings];
126 }
127
128 - (void)mergeEntriesFrom:(NJMapping *)other {
129 if (other)
130 [_entries addEntriesFromDictionary:other->_entries];
131 }
132
133 @end