355dbccdf58951959b52f630ad841665626ccea7
[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)initWithName:(NSString *)name {
21 if ((self = [super init])) {
22 self.name = [name isKindOfClass:NSString.class] ? name : @"Untitled";
23 _entries = [[NSMutableDictionary alloc] init];
24 }
25 return self;
26 }
27
28 - (id)initWithSerialization:(NSDictionary *)serialization
29 mappings:(NSArray *)mappings {
30 if ((self = [self initWithName:serialization[@"name"]])) {
31 NSDictionary *entries = serialization[@"entries"];
32 if ([entries isKindOfClass:NSDictionary.class]) {
33 for (id key in entries) {
34 if ([key isKindOfClass:NSString.class]) {
35 NJOutput *output = [NJOutput outputDeserialize:entries[key]
36 withMappings:mappings];
37 if (output)
38 _entries[key] = output;
39 }
40 }
41 }
42 }
43 return self;
44 }
45
46 - (NJOutput *)objectForKeyedSubscript:(NJInput *)input {
47 return input ? _entries[input.uid] : nil;
48 }
49
50 - (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input {
51 if (input) {
52 if (output)
53 _entries[input.uid] = output;
54 else
55 [_entries removeObjectForKey:input.uid];
56 }
57 }
58
59 - (NSDictionary *)serialize {
60 NSMutableDictionary *entries = [[NSMutableDictionary alloc] initWithCapacity:_entries.count];
61 for (id key in _entries) {
62 id serialized = [_entries[key] serialize];
63 if (serialized)
64 entries[key] = serialized;
65 }
66 return @{ @"name": _name, @"entries": entries };
67 }
68
69 - (BOOL)writeToURL:(NSURL *)url error:(NSError **)error {
70 [NSProcessInfo.processInfo disableSuddenTermination];
71 NSDictionary *serialization = [self serialize];
72 NSData *json = [NSJSONSerialization dataWithJSONObject:serialization
73 options:NSJSONWritingPrettyPrinted
74 error:error];
75 BOOL success = json && [json writeToURL:url options:NSDataWritingAtomic error:error];
76 [NSProcessInfo.processInfo enableSuddenTermination];
77 return success;
78 }
79
80 - (NSUInteger)count {
81 return _entries.count;
82 }
83
84 - (BOOL)hasConflictWith:(NJMapping *)other {
85 if (other.count < self.count)
86 return [other hasConflictWith:self];
87 for (NSString *uid in _entries) {
88 NJOutput *output = other->_entries[uid];
89 if (output && ![output isEqual:_entries[uid]])
90 return YES;
91 }
92 return NO;
93 }
94
95 + (id)mappingWithContentsOfURL:(NSURL *)url mappings:(NSArray *)mappings error:(NSError **)error {
96 NSInputStream *stream = [NSInputStream inputStreamWithURL:url];
97 [stream open];
98 NSDictionary *serialization = stream && !*error
99 ? [NSJSONSerialization JSONObjectWithStream:stream options:0 error:error]
100 : nil;
101 [stream close];
102
103 if (!serialization && error)
104 return nil;
105
106 if (!([serialization isKindOfClass:NSDictionary.class]
107 && [serialization[@"name"] isKindOfClass:NSString.class]
108 && [serialization[@"entries"] isKindOfClass:NSDictionary.class])) {
109 *error = [NSError errorWithDomain:@"Enjoyable"
110 code:0
111 description:@"This isn't a valid mapping file."];
112 return nil;
113 }
114
115 return [[NJMapping alloc] initWithSerialization:serialization
116 mappings:mappings];
117 }
118
119 - (void)mergeEntriesFrom:(NJMapping *)other {
120 if (other)
121 [_entries addEntriesFromDictionary:other->_entries];
122 }
123
124 @end