3d5cd67f5a7470c4d143f8e6173404861444c16d
[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 if ((self = [self initWithName:serialization[@"name"]])) {
38 NSDictionary *entries = serialization[@"entries"];
39 if ([entries isKindOfClass:NSDictionary.class]) {
40 for (id key in entries) {
41 if ([key isKindOfClass:NSString.class]) {
42 NJOutput *output = [NJOutput outputDeserialize:entries[key]];
43 if (output)
44 _entries[key] = output;
45 }
46 }
47 }
48 }
49 return self;
50 }
51
52 - (NJOutput *)objectForKeyedSubscript:(NJInput *)input {
53 return input ? _entries[input.uid] : nil;
54 }
55
56 - (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input {
57 if (input) {
58 if (output)
59 _entries[input.uid] = output;
60 else
61 [_entries removeObjectForKey:input.uid];
62 }
63 }
64
65 - (NSDictionary *)serialize {
66 NSMutableDictionary *entries = [[NSMutableDictionary alloc] initWithCapacity:_entries.count];
67 for (id key in _entries) {
68 id serialized = [_entries[key] serialize];
69 if (serialized)
70 entries[key] = serialized;
71 }
72 return @{ @"name": _name, @"entries": entries };
73 }
74
75 - (BOOL)writeToURL:(NSURL *)url error:(NSError **)error {
76 [NSProcessInfo.processInfo disableSuddenTermination];
77 NSDictionary *serialization = [self serialize];
78 NSData *json = [NSJSONSerialization dataWithJSONObject:serialization
79 options:NSJSONWritingPrettyPrinted
80 error:error];
81 BOOL success = json && [json writeToURL:url options:NSDataWritingAtomic error:error];
82 [NSProcessInfo.processInfo enableSuddenTermination];
83 return success;
84 }
85
86 - (NSUInteger)count {
87 return _entries.count;
88 }
89
90 - (BOOL)hasConflictWith:(NJMapping *)other {
91 if (other.count < self.count)
92 return [other hasConflictWith:self];
93 for (NSString *uid in _entries) {
94 NJOutput *output = other->_entries[uid];
95 if (output && ![output isEqual:_entries[uid]])
96 return YES;
97 }
98 return NO;
99 }
100
101 + (id)mappingWithContentsOfURL:(NSURL *)url error:(NSError **)error {
102 NSInputStream *stream = [NSInputStream inputStreamWithURL:url];
103 [stream open];
104 NSDictionary *serialization = stream && !*error
105 ? [NSJSONSerialization JSONObjectWithStream:stream options:0 error:error]
106 : nil;
107 [stream close];
108
109 if (!serialization && error)
110 return nil;
111
112 if (!([serialization isKindOfClass:NSDictionary.class]
113 && [serialization[@"name"] isKindOfClass:NSString.class]
114 && [serialization[@"entries"] isKindOfClass:NSDictionary.class])) {
115 *error = [NSError errorWithDomain:@"Enjoyable"
116 code:0
117 description:NSLocalizedString(@"invalid mapping file",
118 @"error when imported file was JSON but not a mapping")];
119 return nil;
120 }
121
122 return [[NJMapping alloc] initWithSerialization:serialization];
123 }
124
125 - (void)mergeEntriesFrom:(NJMapping *)other {
126 if (other)
127 [_entries addEntriesFromDictionary:other->_entries];
128 }
129
130 - (void)postLoadProcess:(id <NSFastEnumeration>)allMappings {
131 for (NJOutput *o in _entries.allValues)
132 [o postLoadProcess:allMappings];
133 }
134
135
136 @end