Skip to content

Commit ba7b7ed

Browse files
CUSDK-189: Removed dependency from diff library (#149)
1 parent dfaa65a commit ba7b7ed

File tree

6 files changed

+1066
-10
lines changed

6 files changed

+1066
-10
lines changed

connect/util/Diff.hx

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
/*
2+
This file is part of the Ingram Micro CloudBlue Connect SDK.
3+
Copyright (c) 2019 Ingram Micro. All Rights Reserved.
4+
*/
5+
package connect.util;
6+
7+
import haxe.ds.StringMap;
8+
9+
/**
10+
The `Diff` class stores the difference between two Haxe dynamic objects `first` and `second`.
11+
You can later `apply` the diff object to `first` to obtain `second`, or `swap` the diff and then
12+
`apply` to `second` to get `first`.
13+
**/
14+
class Diff {
15+
private final a:StringMap<Dynamic>; // Additions
16+
private final d:StringMap<Dynamic>; // Deletions
17+
private final c:StringMap<Dynamic>; // Changes
18+
19+
/**
20+
Creates a new `Diff` storing the differences between the two objects passed. The differences basically are:
21+
22+
- Additions: Fields present in `second` that are not present in `first`.
23+
- Deletions: Fields present in `first` that are not present in `second`.
24+
- Changes: Fields whose value has changed between `first` and `second`.
25+
26+
The class is capable of tracking changes inside arrays.
27+
**/
28+
public function new(first:Dynamic, second:Dynamic) {
29+
checkStructs(first, second);
30+
final firstFields = Reflect.fields(first);
31+
final secondFields = Reflect.fields(second);
32+
final addedFields = [for (f in secondFields) if (!Reflect.hasField(first, f)) f];
33+
final deletedFields = [for (f in firstFields) if (!Reflect.hasField(second, f)) f];
34+
final commonFields = [for (f in firstFields) if (Reflect.hasField(second, f)) f];
35+
final changedFields = commonFields.filter(function(f) {
36+
return !areEqual(Reflect.field(first, f), Reflect.field(second, f));
37+
});
38+
39+
// Set additions
40+
this.a = new StringMap<Dynamic>();
41+
Lambda.iter(addedFields, (f) -> this.a.set(f, Reflect.field(second, f)));
42+
43+
// Set deletions
44+
this.d = new StringMap<Dynamic>();
45+
Lambda.iter(deletedFields, (f) -> this.d.set(f, Reflect.field(first, f)));
46+
47+
// Set changes
48+
this.c = new StringMap<Dynamic>();
49+
Lambda.iter(changedFields, function(f) {
50+
final a: Dynamic = Reflect.field(first, f);
51+
final b: Dynamic = Reflect.field(second, f);
52+
if (isStruct(a) && isStruct(b)) {
53+
// Diff
54+
this.c.set(f, new Diff(a, b));
55+
} else if (isArray(a) && isArray(b)) {
56+
// [[a], [d], [c]]
57+
this.c.set(f, compareArrays(a, b));
58+
} else {
59+
// [old, new]
60+
this.c.set(f, [a, b]);
61+
}
62+
});
63+
}
64+
65+
private static function isStruct(value:Dynamic):Bool {
66+
return Type.typeof(value) == TObject;
67+
}
68+
69+
private static function isArray(value:Dynamic):Bool {
70+
return Std.isOfType(value, Array);
71+
}
72+
73+
private static function checkStructs(first:Dynamic, second:Dynamic):Void {
74+
if (!isStruct(first) || !isStruct(second)) {
75+
throw 'Unsupported types in Diff. Values must be structs. '
76+
+ 'Got: ${Type.typeof(first)}, ${Type.typeof(second)}';
77+
}
78+
}
79+
80+
private static function areEqual(first:Dynamic, second:Dynamic):Bool {
81+
return Std.string(Type.typeof(first)) == Std.string(Type.typeof(second))
82+
&& Std.string(first) == Std.string(second);
83+
}
84+
85+
private static function compareArrays(first:Array<Dynamic>, second:Array<Dynamic>):Array<Array<Dynamic>> {
86+
final fixedFirst = (first.length <= second.length)
87+
? first
88+
: first.slice(0, second.length);
89+
final changeList = Lambda.mapi(fixedFirst, function(i, el): Array<Dynamic> {
90+
final a: Dynamic = el;
91+
final b: Dynamic = second[i];
92+
if (areEqual(a, b)) {
93+
return null;
94+
} else {
95+
if (isStruct(a) && isStruct(b)) {
96+
return [i, new Diff(a, b)];
97+
} else if (isArray(a) && isArray(b)) {
98+
return [i, compareArrays(a, b)];
99+
} else {
100+
return [i, a, b];
101+
}
102+
}
103+
});
104+
final changes = [for (el in changeList) el].filter(el -> el != null);
105+
return [
106+
second.slice(first.length),
107+
first.slice(second.length),
108+
changes
109+
];
110+
}
111+
112+
/**
113+
Applies to changes in `this` `Diff` to the dynamic object passed as argument.
114+
If this method is called on the `first` object passed when constructing `this`,
115+
then an object identical to `second` is returned.
116+
**/
117+
public function apply(obj:Dynamic):Dynamic {
118+
final out = Reflect.copy(obj);
119+
120+
// Additions
121+
final addedKeys = [for (k in this.a.keys()) k];
122+
Lambda.iter(addedKeys, k -> Reflect.setField(out, k, this.a.get(k)));
123+
124+
// Deletions
125+
final deletedKeys = [for (k in this.d.keys()) k];
126+
Lambda.iter(deletedKeys, k -> Reflect.deleteField(out, k));
127+
128+
// Changes
129+
final changedKeys = [for (k in this.c.keys()) k];
130+
Lambda.iter(changedKeys, function(k) {
131+
final change = this.c.get(k);
132+
if (Std.isOfType(change, Array)) {
133+
if (change.length == 2) {
134+
// [old, new]
135+
Reflect.setField(out, k, cast(change, Array<Dynamic>)[1]);
136+
} else {
137+
// [[a], [d], [c]]
138+
final field = Reflect.field(out, k);
139+
final original = (field != null) ? field : [];
140+
Reflect.setField(out, k, applyArray(original, change));
141+
}
142+
} else {
143+
// Diff
144+
final field = Reflect.field(out, k);
145+
final original = (field != null) ? field : {};
146+
Reflect.setField(out, k, change.apply(original));
147+
}
148+
});
149+
150+
return out;
151+
}
152+
153+
private static function applyArray(obj:Array<Dynamic>, arr:Array<Array<Dynamic>>):Array<Dynamic> {
154+
// Apply deletions
155+
final slice = obj.slice(0, obj.length - arr[1].length);
156+
final deleted = (slice != null) ? slice : [];
157+
158+
// Apply additions
159+
final added = deleted.concat(arr[0]);
160+
161+
// Apply changes
162+
final out = added;
163+
Lambda.iter(arr[2], function(change: Array<Dynamic>) {
164+
final i = change[0];
165+
final originalArray: Dynamic = (out.length > i) ? out[i] : [];
166+
final originalObject = (out.length > i) ? out[i] : {};
167+
out[i] = (change.length == 3)
168+
? change[2] // [i, old, new]
169+
: (Std.isOfType(change[1], Array))
170+
? applyArray(originalArray, change[1]) // [i, [[a], [d], [c]]]
171+
: change[1].apply(originalObject); // [i, Diff]
172+
});
173+
174+
return out;
175+
}
176+
177+
/**
178+
Returns a new `Diff` that reverts the changes made by this one. If `apply` is called on
179+
the returned `Diff` passing the `second` argument used to construct `this`, then and object
180+
identical to the `first` argument used to construct `this` will be returned by `apply`.
181+
**/
182+
public function swap():Diff {
183+
final additions = this.d;
184+
final deletions = this.a;
185+
final changes = new StringMap<Dynamic>();
186+
final changedKeys = [for (k in this.c.keys()) k];
187+
Lambda.iter(changedKeys, function(k) {
188+
final change = this.c.get(k);
189+
if (Std.isOfType(change, Array)) {
190+
if (change.length == 2) {
191+
// [old, new]
192+
changes.set(k, [cast(change, Array<Dynamic>)[1], cast(change, Array<Dynamic>)[0]]);
193+
} else {
194+
// [[a], [d], [c]]
195+
changes.set(k, swapArray(change));
196+
}
197+
} else {
198+
// Diff
199+
changes.set(k, change.swap());
200+
}
201+
});
202+
203+
final diff = Type.createEmptyInstance(Diff);
204+
Reflect.setField(diff, 'a', additions);
205+
Reflect.setField(diff, 'd', deletions);
206+
Reflect.setField(diff, 'c', changes);
207+
return diff;
208+
}
209+
210+
private static function swapArray(arr:Array<Array<Dynamic>>):Array<Array<Dynamic>> {
211+
final additions = arr[1];
212+
final deletions = arr[0];
213+
final changes = arr[2];
214+
final swappedChanges: Array<Array<Dynamic>> = changes.map(function(change: Array<Dynamic>) {
215+
final i: Dynamic = change[0];
216+
final first: Dynamic = change[1];
217+
final second: Dynamic = change[2];
218+
final swappedChange: Array<Dynamic> =
219+
(change.length == 3) ?
220+
[i, second, first]
221+
: (Std.isOfType(first, Array)) ?
222+
[i, swapArray(first)]
223+
:
224+
[i, first.swap()];
225+
return swappedChange;
226+
});
227+
228+
return [
229+
additions,
230+
deletions,
231+
swappedChanges
232+
];
233+
}
234+
235+
/**
236+
Returns a string with the Json representation of `this` `Diff`.
237+
**/
238+
public function toString():String {
239+
return haxe.Json.stringify(this.toObject());
240+
}
241+
242+
private function toObject():Dynamic {
243+
final obj = {
244+
a: mapToObject(this.a),
245+
d: mapToObject(this.d),
246+
c: {}
247+
};
248+
final changedKeys = [for (k in this.c.keys()) k];
249+
Lambda.iter(changedKeys, function(key) {
250+
final value = this.c.get(key);
251+
if (Std.isOfType(value, Diff)) {
252+
// Diff
253+
Reflect.setField(obj.c, key, value.toObject());
254+
} else if (isArray(value) && value.length == 3) {
255+
// [[a], [d], [c]]
256+
Reflect.setField(obj.c, key, changeArrayToObject(value));
257+
} else {
258+
// [old, new]
259+
Reflect.setField(obj.c, key, value);
260+
}
261+
});
262+
return obj;
263+
}
264+
265+
private static function mapToObject(map:StringMap<Dynamic>):Dynamic {
266+
final keys = [for (k in map.keys()) k];
267+
final obj = {};
268+
Lambda.iter(keys, key -> Reflect.setField(obj, key, map.get(key)));
269+
return obj;
270+
}
271+
272+
private static function changeArrayToObject(arr:Array<Array<Dynamic>>):Array<Array<Dynamic>> {
273+
final changesList = Lambda.map(arr[2], function(el:Array<Dynamic>):Array<Dynamic> {
274+
if (Std.isOfType(el[1], Diff)) {
275+
return [el[0], el[1].toObject()];
276+
} else if (isArray(el[1])) {
277+
return [el[0], changeArrayToObject(untyped el[1])];
278+
} else {
279+
return el;
280+
}
281+
});
282+
final changes = [for (change in changesList) change];
283+
final arr = [
284+
arr[0],
285+
arr[1],
286+
changes
287+
];
288+
return arr;
289+
}
290+
}

connect/util/Util.hx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ class Util {
197197
*/
198198
public static function createObjectDiff(object:Dynamic, previous:Dynamic):Dynamic {
199199
return Util.addIdsToObject(
200-
new diff.Diff(previous, object).apply({id: object.id}),
200+
new Diff(previous, object).apply({id: object.id}),
201201
previous);
202202
}
203203

package.hxml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
-main Connect
22
-dce no
3-
-lib diff:1.0.0
43
--each
54

65
-D cslib

0 commit comments

Comments
 (0)