forked from TynanSylvester/TyDSharp
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Inheritance.cs
335 lines (290 loc) · 12.5 KB
/
Inheritance.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
using System;
using System.Collections.Generic;
using System.Linq;
namespace Tyd
{
/// <summary>
/// Handles inheritance between TydNodes via handle and source _attributes.
///
/// To use Inheritance:
/// 1. Call Initialize().
/// 2. Register all the _nodes you want to interact with each other.
/// 3. Call ResolveAll. This will modify the registered _nodes in-place with any inheritance data.
/// 4. Call Complete().
///
/// It's recommended you use try/catch to ensure that Complete is always called.
/// </summary>
public static class Inheritance
{
private class InheritanceNode
{
public TydCollection TydNode;
public bool Resolved;
public InheritanceNode Source; // Node from which I inherit.
private List<InheritanceNode> _heirs = null; // Nodes which inherit from me.
public InheritanceNode(TydCollection tydNode)
{
this.TydNode = tydNode;
}
public int HeirCount
{
get { return _heirs != null ? _heirs.Count : 0; }
}
public InheritanceNode GetHeir(int index)
{
return _heirs[index];
}
public void AddHeir(InheritanceNode n)
{
if (_heirs == null)
{
_heirs = new List<InheritanceNode>();
}
_heirs.Add(n);
}
public override string ToString()
{
return TydNode.ToString();
}
}
//Working vars
private static List<InheritanceNode> nodesUnresolved = new List<InheritanceNode>();
private static Dictionary<TydNode, InheritanceNode> nodesResolved = new Dictionary<TydNode, InheritanceNode>();
private static Dictionary<string, InheritanceNode> nodesByHandle = new Dictionary<string, InheritanceNode>();
public static void Clear()
{
nodesResolved.Clear();
nodesUnresolved.Clear();
nodesByHandle.Clear();
}
///<summary>
/// Registers a single node.
/// When we resolve later, we'll be able to use this node as a source.
///</summary>
public static void Register(TydCollection colNode)
{
//If the node has no handle, and no source, we can ignore it since it's not connected to inheritance at all.
var nodeHandle = colNode.AttributeHandle;
var nodeSource = colNode.AttributeSource;
if (nodeHandle == null && nodeSource == null)
{
return;
}
//Ensure we're don't have two _nodes of the same handle
if (nodeHandle != null && nodesByHandle.ContainsKey(nodeHandle))
{
throw new Exception(string.Format("Tyd error: Multiple Tyd _nodes with the same handle {0}.", nodeHandle));
}
//Make an inheritance node for the Tyd node
var newNode = new InheritanceNode(colNode);
nodesUnresolved.Add(newNode);
if (nodeHandle != null)
{
nodesByHandle.Add(nodeHandle, newNode);
}
}
///<summary>
/// Registers all _nodes from doc.
/// When we resolve later, we'll be able to use the _nodes in this document as a sources.
///</summary>
public static void RegisterAllFrom(TydDocument doc)
{
for (var i = 0; i < doc.Count; i++)
{
var tydCol = doc[i] as TydCollection;
if (tydCol != null)
{
Register(tydCol);
}
}
}
///<summary>
/// Resolves all registered _nodes.
///</summary>
public static void ResolveAll()
{
LinkAllInheritanceNodes();
ResolveAllUnresolvedInheritanceNodes();
}
// Merge all unresolved _nodes with their source _nodes.
private static void ResolveAllUnresolvedInheritanceNodes()
{
// find roots from which we'll start resolving _nodes,
// a node is a root node if it has null source or its source has been already resolved,
// this method works only for single inheritance!
var roots = nodesUnresolved.Where(x => x.Source == null || x.Source.Resolved).ToList(); // important to make a copy
for (var i = 0; i < roots.Count; i++)
{
ResolveInheritanceNodeAndHeirs(roots[i]);
}
// check if there are any unresolved _nodes (if there are, then it means that there is a cycle),
// and move _nodes to resolved _nodes collection
for (var i = 0; i < nodesUnresolved.Count; i++)
{
if (!nodesUnresolved[i].Resolved)
{
throw new FormatException("Tyd error: Cyclic inheritance detected for node:\n" + nodesUnresolved[i].TydNode.FullTyd);
//continue;
}
nodesResolved.Add(nodesUnresolved[i].TydNode, nodesUnresolved[i]);
}
nodesUnresolved.Clear();
}
// Link all unresolved _nodes to their sources and heirs.
private static void LinkAllInheritanceNodes()
{
for (var i = 0; i < nodesUnresolved.Count; i++)
{
var urn = nodesUnresolved[i];
var attSource = urn.TydNode.AttributeSource;
if (attSource == null)
{
continue;
}
if (!nodesByHandle.TryGetValue(attSource, out urn.Source))
{
throw new Exception(string.Format("Could not find source node named '{0}' for Tyd node: {1}", attSource, urn.TydNode.FullTyd));
}
if (urn.Source != null)
{
urn.Source.AddHeir(urn);
}
}
}
///<summary>
/// Resolves given node and then all its heir _nodes recursively using DFS.
///</summary>
private static void ResolveInheritanceNodeAndHeirs(InheritanceNode node)
{
//Error check
// if we've reached a resolved node by traversing the tree, then it means
// that there's a cycle, note that we're not reporting the full cycle in
// the error message here, but only the last node which created a cycle
if (node.Resolved)
{
throw new Exception(string.Format("Cyclic inheritance detected for Tyd node:\n{0}", node.TydNode.FullTyd));
}
//Resolve this node
{
if (node.Source == null)
{
// No source - Just use the original node
node.Resolved = true;
}
else
{
//Source exists - We now inherit from it
//We must use source's RESOLVED node here because our source can have its own source.
if (!node.Source.Resolved)
{
throw new Exception(string.Format(
"Tried to resolve Tyd inheritance node {0} whose source has not been resolved yet. This means that this method was called in incorrect order.",
node));
}
CheckForDuplicateNodes(node.TydNode);
node.Resolved = true;
//Write resolved node's class attribute
//Original takes precedence over source; source takes precedence over default
var attClass = node.TydNode.AttributeClass ?? node.Source.TydNode.AttributeClass;
node.TydNode.SetAttribute("class", attClass);
//Apply inheritance from source to node
ApplyInheritance(node.Source.TydNode, node.TydNode);
}
}
//Recur to the heirs and resolve them too
for (var i = 0; i < node.HeirCount; i++)
{
ResolveInheritanceNodeAndHeirs(node.GetHeir(i));
}
}
///<summary>
/// Copies all child _nodes from source into heir, recursively.
/// -If a node appears only in source or only in heir, it is included.
/// -If a list appears in both source and heir, source's entries are appended to heir's entries.
/// -If a non-list node appears in both source and heir, heir's node is overwritten.
///</summary>
private static void ApplyInheritance(TydNode source, TydNode heir)
{
try
{
//They're either strings or nulls: We just keep the existing heir's value
if (source is TydString)
{
return;
}
//Heir has noinherit attribute: Skip this inheritance
{
var heirCol = heir as TydCollection;
if (heirCol != null && heirCol.AttributeNoInherit)
{
return;
}
}
//They're tables: Combine all children of source and heir. Unique-name source nodes are prepended
{
var sourceObj = source as TydTable;
if (sourceObj != null)
{
var heirTable = (TydTable)heir;
for (var i = 0; i < sourceObj.Count; i++)
{
var sourceChild = sourceObj[i];
var heirMatchingChild = heirTable[sourceChild.Name];
if (heirMatchingChild != null)
{
ApplyInheritance(sourceChild, heirMatchingChild);
}
else
{
heirTable.InsertChild(sourceChild.DeepClone(), 0);
}
}
return;
}
}
//They're lists: Prepend source's children before heir's children
{
var sourceList = source as TydList;
if (sourceList != null)
{
var heirList = (TydList)heir;
for (var i = 0; i < sourceList.Count; i++)
{
//Insert at i so the nodes stay in the same order from source to heir
heirList.InsertChild(sourceList[i].DeepClone(), i);
}
return;
}
}
}
catch (Exception e)
{
throw new Exception("ApplyInheritance exception: " + e + ".\nsource: (" + source + ")\n" + TydToText.Write(source, true) + "\ntarget: (" + heir + ")\n" + TydToText.Write(heir, true));
}
}
private static HashSet<string> tempUsedNodeNames = new HashSet<string>();
private static void CheckForDuplicateNodes(TydCollection originalNode)
{
//This is needed despite another check elsewhere
//Because the source-data-combination process wipes out duplicate Tyd data
tempUsedNodeNames.Clear();
for (var i = 0; i < originalNode.Count; i++)
{
var node = originalNode[i];
if (node.Name == null)
{
continue;
}
if (tempUsedNodeNames.Contains(node.Name))
{
throw new FormatException("Tyd error: Duplicate Tyd node _name " + node.Name + " in this Tyd block: " + originalNode);
}
else
{
tempUsedNodeNames.Add(node.Name);
}
}
tempUsedNodeNames.Clear();
}
}
}