1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.IO;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure;
12 using Antmicro.Renode.Exceptions;
13 using Antmicro.Renode.Peripherals;
14 using Antmicro.Renode.Utilities;
15 using System.Collections.Generic;
16 using System.Reflection;
17 using System.Linq;
18 using System.Text;
19 using System.Runtime.Serialization;
20 using Dynamitey;
21 using Microsoft.CSharp.RuntimeBinder;
22 
23 //HACK!Type.IsPrimitive/IsEnum is used in some test due to the bug in mono 3.2.0.
24 //It doesn't allow to compare dynamic containing a primitive with null.
25 //This is fixed at least in 3.4.1. Remove this test when 3.2.0 gets obsolete.
26 //Oh, and it crashes for nullables too. Doh.
27 
28 namespace Antmicro.Renode.Config.Devices
29 {
30     public class DevicesConfig
31     {
32         private readonly Dictionary<string, List<DeviceInfo>> groups = new Dictionary<string, List<DeviceInfo>>();
33         private Dictionary<KeyValuePair<string, dynamic>, string> deferred = new Dictionary<KeyValuePair<string, dynamic>, string>();
34         private List<DeviceInfo> deviceList = new List<DeviceInfo>();
35         private static string DefaultNamespace = "Antmicro.Renode.Peripherals.";
36 
37         public List<DeviceInfo> DeviceList{ get { return deviceList; } }
38 
FailDevice(string deviceName, string field = null, Exception e = null)39         private static void FailDevice(string deviceName, string field = null, Exception e = null)
40         {
41             var msg = new StringBuilder(String.Format("Could not create peripheral from node {0}", deviceName));
42             if(field != null)
43             {
44                 msg.Append(String.Format(" in section {0}.", field));
45             }
46 
47             if(e != null)
48             {
49                 msg.Append(" Exception message: ");
50                 if(!(e is TargetInvocationException))
51                 {
52                     msg.Append(String.Format("{0}. ", e.Message));
53                 }
54                 if(e.InnerException != null)
55                 {
56                     msg.Append(String.Format("{0}. ", e.InnerException.Message));
57                 }
58                 if(!(e is RecoverableException))
59                 {
60                     throw new InvalidOperationException(msg.ToString());
61                 }
62             }
63             throw new RecoverableException(msg.ToString());
64         }
65 
GetDeviceTypeFromName(string typeName)66         private static Type GetDeviceTypeFromName(string typeName)
67         {
68             var extendedTypeName = typeName.StartsWith(DefaultNamespace, StringComparison.Ordinal) ? typeName : DefaultNamespace + typeName;
69 
70             var manager = TypeManager.Instance;
71             return manager.TryGetTypeByName(typeName) ?? manager.TryGetTypeByName(extendedTypeName);
72         }
73 
InitializeDevice(KeyValuePair<string, dynamic> description, string groupName = null)74         private bool InitializeDevice(KeyValuePair<string, dynamic> description, string groupName = null)
75         {
76             if(description.Value is JsonArray)
77             {
78                 if(!groups.ContainsKey(description.Key))
79                 {
80                     groups.Add(description.Key, new List<DeviceInfo>());
81                 }
82                 var x = groups[description.Key];
83 
84                 var any = false;
85                 foreach(var element in description.Value)
86                 {
87                     var dev = InitializeSingleDevice(new KeyValuePair<string, dynamic>(element.Keys[0], element.Values[0]), description.Key);
88                     if(dev != null)
89                     {
90                         deviceList.Add(dev);
91                         x.Add(dev);
92                         any = true;
93                     }
94                 }
95 
96                 return any;
97             }
98             else if(description.Value is JsonObject)
99             {
100                 var dev = InitializeSingleDevice(description);
101                 if(dev != null)
102                 {
103                     deviceList.Add(dev);
104                     if(groupName != null)
105                     {
106                         if(!groups.ContainsKey(groupName))
107                         {
108                             groups.Add(groupName, new List<DeviceInfo>());
109                         }
110                         groups[groupName].Add(dev);
111                     }
112                     return true;
113                 }
114             }
115             else
116             {
117                 FailDevice(description.Key);
118             }
119 
120             return false;
121         }
122 
123         /// Required/possible nodes:
124         /// _type
125         /// _irq/_gpio - optional
126         /// _connection - optional?
127         /// ctorParam
128         /// PropertyWithSetter
InitializeSingleDevice(KeyValuePair<string, dynamic> device, string groupName = null)129         private DeviceInfo InitializeSingleDevice(KeyValuePair<string, dynamic> device, string groupName = null)
130         {
131             var info = new DeviceInfo();
132             info.Name = device.Key;
133             var devContent = device.Value;
134             if(devContent == null)
135             {
136                 FailDevice(info.Name);
137             }
138 
139             //Type
140             if(!devContent.ContainsKey(TYPE_NODE))
141             {
142                 FailDevice(info.Name, TYPE_NODE);
143             }
144             var typeName = (string)devContent[TYPE_NODE];
145 
146             var devType = GetDeviceTypeFromName(typeName);
147             if(devType == null)
148             {
149                 FailDevice(info.Name, TYPE_NODE);
150             }
151 
152             object peripheral;
153             //Constructor
154             if(!TryInitializeCtor(devType, devContent, out peripheral))
155             {
156                 FailDevice(info.Name, "constructor_invoke");
157             }
158             if(peripheral == null)
159             {
160                 // special case when construction of the object has been deferred
161                 deferred.Add(device, groupName);
162                 return null;
163             }
164             devContent.Remove(TYPE_NODE);
165 
166             info.Peripheral = (IPeripheral)peripheral;
167 
168             //Properties
169             try
170             {
171                 InitializeProperties(info.Peripheral, devContent);
172             }
173             catch(InvalidOperationException e)
174             {
175                 FailDevice(info.Name, e.Message, e.InnerException);
176             }
177 
178             //GPIOs
179             if(devContent.ContainsKey(IRQ_NODE))
180             {
181                 info.AddIrq(IRQ_NODE, devContent[IRQ_NODE]);
182                 devContent.Remove(IRQ_NODE);
183             }
184             else if(devContent.ContainsKey(GPIO_NODE))
185             {
186                 info.AddIrq(GPIO_NODE, devContent[GPIO_NODE]);
187                 devContent.Remove(GPIO_NODE);
188             }
189 
190             //IRQs From
191             if(devContent.ContainsKey(IRQ_FROM_NODE))
192             {
193                 info.AddIrqFrom(IRQ_FROM_NODE, devContent[IRQ_FROM_NODE]);
194                 devContent.Remove(IRQ_FROM_NODE);
195             }
196             else if(devContent.ContainsKey(GPIO_FROM_NODE))
197             {
198                 info.AddIrqFrom(GPIO_FROM_NODE, devContent[GPIO_FROM_NODE]);
199                 devContent.Remove(GPIO_FROM_NODE);
200             }
201 
202             //Connections
203             if(devContent.ContainsKey(CONNECTION_NODE))
204             {
205                 InitializeConnections(info, devContent[CONNECTION_NODE]);
206                 devContent.Remove(CONNECTION_NODE);
207             }
208 
209             return info;
210         }
211 
InitializeGPIOsFrom(DeviceInfo device)212         private void InitializeGPIOsFrom(DeviceInfo device)
213         {
214             foreach(var nodeName in device.IrqsFrom.Keys)
215             {
216                 var gpioReceiver = device.Peripheral as IGPIOReceiver;
217                 if(gpioReceiver == null)
218                 {
219                     FailDevice(device.Name, nodeName);
220                 }
221 
222                 var irqs = device.IrqsFrom[nodeName];
223                 if(irqs == null)
224                 {
225                     FailDevice(device.Name, nodeName);
226                 }
227 
228                 foreach(var source in irqs.Keys)
229                 {
230                     var sourceIrqs = irqs[source] as List<dynamic>;
231                     if(sourceIrqs == null)
232                     {
233                         FailDevice(device.Name, nodeName + ": " + source);
234                     }
235 
236                     IPeripheral sourcePeripheral;
237 
238                     var fromList = deviceList.SingleOrDefault(x => x.Name == source);
239 
240                     if(fromList != null)
241                     {
242                         sourcePeripheral = (IGPIOReceiver)fromList.Peripheral;
243                     }
244                     else if(!machine.TryGetByName<IPeripheral>(source, out sourcePeripheral))
245                     {
246                         FailDevice(device.Name, nodeName + ": " + source);
247                     }
248                     if(sourcePeripheral is ILocalGPIOReceiver && source.Length == 2)
249                     {
250                         sourcePeripheral = ((ILocalGPIOReceiver)sourcePeripheral).GetLocalReceiver(int.Parse(source[1]));
251                     }
252 
253                     var props = sourcePeripheral.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
254                     var connectors = props
255                         .Where(x => typeof(GPIO).IsAssignableFrom(x.PropertyType)).ToArray();
256                     PropertyInfo defaultConnector = null;
257                     if(connectors.Count() == 1)
258                     {
259                         defaultConnector = connectors.First();
260                     }
261 
262                     try
263                     {
264                         if(sourceIrqs.All(x => x is JsonArray))
265                         {
266                             foreach(var irqEntry in sourceIrqs)
267                             {
268                                 InitializeGPIO(sourcePeripheral, device.Name, gpioReceiver, irqEntry.ToDynamic(), defaultConnector);
269                             }
270                         }
271                         else
272                         {
273                             InitializeGPIO(sourcePeripheral, device.Name, gpioReceiver, ((JsonArray)sourceIrqs).ToDynamic(), defaultConnector);
274                         }
275                     }
276                     catch(ArgumentException)
277                     {
278                         FailDevice(device.Name, nodeName + ": " + source);
279                     }
280                 }
281             }
282         }
283 
InitializeGPIOs(DeviceInfo device)284         private void InitializeGPIOs(DeviceInfo device)
285         {
286             foreach(var nodeName in device.Irqs.Keys)
287             {
288                 var irqs = device.Irqs[nodeName];
289                 if(irqs == null)
290                 {
291                     FailDevice(device.Name, nodeName);
292                 }
293                 var props = device.Peripheral.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
294                 var connectors = props
295                 .Where(x => typeof(GPIO).IsAssignableFrom(x.PropertyType)).ToArray();
296                 PropertyInfo defaultConnector = null;
297                 if(connectors.Count() == 1)
298                 {
299                     defaultConnector = connectors.First();
300                 }
301 
302                 foreach(var controller in irqs.Keys)
303                 {
304                     var controllerIrqs = irqs[controller] as List<dynamic>;
305                     if(controllerIrqs == null)
306                     {
307                         FailDevice(device.Name, nodeName + ": " + controller);
308                     }
309 
310                     var controllerElements = controller.Split('#');
311                     if(controllerElements.Length > 2)
312                     {
313                         FailDevice(device.Name, nodeName + ": " + controller);
314                     }
315 
316                     IGPIOReceiver receiver;
317 
318                     var fromList = deviceList.SingleOrDefault(x => x.Name == controllerElements[0]);
319 
320                     if(fromList != null && fromList.Peripheral is IGPIOReceiver)
321                     {
322                         receiver = (IGPIOReceiver)fromList.Peripheral;
323                     }
324                     else if(!machine.TryGetByName<IGPIOReceiver>(controllerElements[0], out receiver))
325                     {
326                         FailDevice(device.Name, nodeName + ": " + controller);
327                     }
328                     if(receiver is ILocalGPIOReceiver && controllerElements.Length == 2)
329                     {
330                         receiver = ((ILocalGPIOReceiver)receiver).GetLocalReceiver(int.Parse(controllerElements[1]));
331                     }
332 
333                     try
334                     {
335                         if(controllerIrqs.All(x => x is JsonArray))
336                         {
337                             foreach(var irqEntry in controllerIrqs)
338                             {
339                                 InitializeGPIO(device.Peripheral, device.Name, receiver, irqEntry.ToDynamic(), defaultConnector);
340                             }
341                         }
342                         else
343                         {
344                             InitializeGPIO(device.Peripheral, device.Name, receiver, ((JsonArray)controllerIrqs).ToDynamic(), defaultConnector);
345                         }
346                     }
347                     catch(ArgumentException)
348                     {
349                         FailDevice(device.Name, nodeName + ": " + controller);
350                     }
351                 }
352             }
353         }
354 
355         //[source,dest] or [dest] with non-null defaultConnector
InitializeGPIO(IPeripheral device, string deviceName, IGPIOReceiver receiver, IList<int> irqEntry, PropertyInfo defaultConnector)356         void InitializeGPIO(IPeripheral device, string deviceName, IGPIOReceiver receiver, IList<int> irqEntry, PropertyInfo defaultConnector)
357         {
358             var periByNumber = device as INumberedGPIOOutput;
359             if(irqEntry.Count == 2 && periByNumber != null)
360             {
361                 periByNumber.Connections[irqEntry[0]].Connect(receiver, irqEntry[1]);
362             }
363             else if(irqEntry.Count == 1 && defaultConnector != null)
364             {
365                 var gpioField = defaultConnector.GetValue(device, null) as GPIO;
366                 if(gpioField == null)
367                 {
368                     FailDevice(deviceName);
369                 }
370                 gpioField.Connect(receiver, irqEntry[0]);
371             }
372             else
373             {
374                 throw new ArgumentException();
375             }
376         }
377 
InitializeGPIO(IPeripheral device, string deviceName, IGPIOReceiver receiver, IList<object> irqEntry, PropertyInfo defaultConnector)378         void InitializeGPIO(IPeripheral device, string deviceName, IGPIOReceiver receiver, IList<object> irqEntry, PropertyInfo defaultConnector)
379         {
380             if(!(irqEntry[0] is string && irqEntry[1] is int))
381             {
382                 throw new ArgumentException();
383             }
384             //May throw AmbiguousMatchException - then use BindingFlags.DeclaredOnly or sth
385             var connector = device.GetType().GetProperty(irqEntry[0] as string);
386             if(connector == null)
387             {
388                 throw new ArgumentException();
389             }
390             var gpio = connector.GetValue(device, null) as GPIO;
391             if(gpio == null)
392             {
393                 FailDevice(deviceName);
394             }
395             gpio.Connect(receiver, (int)irqEntry[1]);
396 
397         }
398 
IsShortNotation(IPeripheral peripheral, PropertyInfo defaultConnector, IList<dynamic> entry)399         private bool IsShortNotation(IPeripheral peripheral, PropertyInfo defaultConnector, IList<dynamic> entry)
400         {
401             return (peripheral is INumberedGPIOOutput && entry.Count == 2 && entry.All(x => x is int))
402             || (defaultConnector != null && entry.Count == 1 && entry[0] is int)
403             || (entry.Count == 2 && entry[0] is String && entry[1] is int);
404         }
405 
InitializeConnections(DeviceInfo device, IDictionary<string, dynamic> connections)406         private static void InitializeConnections(DeviceInfo device, IDictionary<string, dynamic> connections)
407         {
408             if(connections == null)
409             {
410                 FailDevice(device.Name, CONNECTION_NODE);
411             }
412             foreach(var container in connections.Keys)
413             {
414                 var conDict = connections[container];
415                 device.AddConnection(container, conDict);
416             }
417         }
418 
InitializeConnections(DeviceInfo device, string connection)419         private static void InitializeConnections(DeviceInfo device, string connection)
420         {
421             if(string.IsNullOrWhiteSpace(connection))
422             {
423                 FailDevice(device.Name, CONNECTION_NODE);
424             }
425             device.AddConnection(connection);
426         }
427 
InitializeProperties(object device, IDictionary<string, dynamic> node)428         private void InitializeProperties(object device, IDictionary<string, dynamic> node)
429         {
430             foreach(var item in node.Keys.Where(x=>Char.IsUpper(x,0)))
431             {
432                 var value = node[item];
433                 try
434                 {
435                     Dynamic.InvokeSet(device, item, value);
436                 }
437                 catch(Exception e)
438                 {
439                     throw new RecoverableException(item, e);
440                 }
441             }
442         }
443 
DevicesConfig(string text, IMachine machine)444         public DevicesConfig(string text, IMachine machine)
445         {
446             try
447             {
448                 var devices = SimpleJson.DeserializeObject<dynamic>(text);
449                 this.machine = machine;
450                 //Every main node is one peripheral/device
451                 foreach(var dev in devices)
452                 {
453                     InitializeDevice(dev);
454                 }
455 
456                 while(deferred.Count > 0)
457                 {
458                     var lastCount = deferred.Count;
459                     foreach(var deferredDevice in deferred.ToList())
460                     {
461                         if(InitializeDevice(deferredDevice.Key, deferredDevice.Value))
462                         {
463                             deferred.Remove(deferredDevice.Key);
464                         }
465                     }
466 
467                     if(lastCount == deferred.Count)
468                     {
469                         throw new ConstructionException("The provided configuration is not consistent. Some devices could not have been created due to wrong references.");
470                     }
471                 }
472 
473                 //Initialize connections
474                 while(deviceList.Any(x => !x.IsRegistered))
475                 {
476                     var anyChange = false;
477                     //setup connections
478                     foreach(var periConn in deviceList.Where(x=> !x.IsRegistered && x.HasConnections))
479                     {
480                         var parents = new Dictionary<string, IPeripheral>();
481                         foreach(var conn in periConn.Connections.Select(x=>x.Key))
482                         {
483                             var fromList = deviceList.SingleOrDefault(x => x.Name == conn);
484                             if(fromList != null)
485                             {
486                                 parents.Add(conn, fromList.Peripheral);
487                             }
488                             else
489                             {
490                                 IPeripheral candidate;
491                                 if(!machine.TryGetByName(conn, out candidate))
492                                 {
493                                     FailDevice(periConn.Name, "connection to " + conn, null);
494                                 }
495                                 parents.Add(conn, candidate);
496                             }
497                         }
498 
499                         var canBeRegistered = parents.All(x => machine.IsRegistered(x.Value));
500                         if(canBeRegistered)
501                         {
502                             RegisterInParents(periConn, parents);
503                             periConn.IsRegistered = true;
504                             anyChange = true;
505                         }
506                     }
507                     if(!anyChange)
508                     {
509                         var invalidDevices = deviceList.Where(x => !x.IsRegistered).Select(x => x.Name).Aggregate((x, y) => x + ", " + y);
510                         throw new RegistrationException("The " +
511                         "provided configuration is not consistent. The following devices could not have been registered: "
512                         + invalidDevices
513                         );
514                     }
515                 }
516 
517                 foreach(var device in deviceList.Where(x=>x.Irqs.Any()))
518                 {
519                     InitializeGPIOs(device);
520                 }
521 
522                 foreach(var device in deviceList.Where(x=>x.IrqsFrom.Any()))
523                 {
524                     InitializeGPIOsFrom(device);
525                 }
526             }
527             catch(SerializationException e)
528             {
529                 throw new RecoverableException("Invalid JSON string.", e);
530             }
531             catch(RuntimeBinderException e)
532             {
533                 throw new RecoverableException("The config file could not be analyzed. You should reset your current emulation.", e);
534             }
535 
536             foreach(var group in groups)
537             {
538                 machine.PeripheralsGroups.GetOrCreate(group.Key, group.Value.Select(x => x.Peripheral));
539             }
540 
541             foreach(var device in deviceList)
542             {
543                 machine.SetLocalName(device.Peripheral, device.Name);
544             }
545         }
546 
GetShortInfo(string filename)547         public static IEnumerable<DeviceInfo> GetShortInfo(string filename)
548         {
549             var devices = ((JsonObject)SimpleJson.DeserializeObject<dynamic>(File.ReadAllText(filename))).ToList();
550             // flattening peripheral groups
551             var arrays = devices.Where(d => d.Value is JsonArray).ToList();
552             arrays.ForEach(a => devices.Remove(a));
553             arrays.Select(a => a.Value).Cast<JsonArray>().SelectMany(y => y).Cast<JsonObject>().ToList().ForEach(x => devices.AddRange(x));
554 
555             foreach(var device in devices)
556             {
557                 var info = new DeviceInfo();
558                 info.Name = device.Key;
559                 dynamic devContent = device.Value;
560                 if(devContent == null)
561                 {
562                     FailDevice(device.Key);
563                 }
564 
565                 if(!devContent.ContainsKey(TYPE_NODE))
566                 {
567                     FailDevice(device.Key, TYPE_NODE);
568                 }
569                 var typeName = (string)devContent[TYPE_NODE];
570 
571                 var devType = GetDeviceTypeFromName(typeName);
572                 if(devType == null)
573                 {
574                     FailDevice(device.Key, TYPE_NODE);
575                 }
576                 info.Type = devType;
577 
578                 if(devContent.ContainsKey(CONNECTION_NODE))
579                 {
580                     InitializeConnections(info, devContent[CONNECTION_NODE]);
581                 }
582                 yield return info;
583             }
584         }
585 
RegisterInParents(DeviceInfo device, IDictionary<string, IPeripheral> parents)586         private void RegisterInParents(DeviceInfo device, IDictionary<string, IPeripheral> parents)
587         {
588             foreach(var parentName in device.Connections.Keys)
589             {
590                 //TODO: nongeneric version
591                 var parent = parents.Single(x => x.Key == parentName).Value;
592                 var connections = device.Connections[parentName];
593                 var ifaces = parent.GetType().GetInterfaces().Where(x => IsSpecializationOfRawGeneric(typeof(IPeripheralRegister<,>), x)).ToList();
594                 var ifaceCandidates = ifaces
595                     .Where(x => x.GetGenericArguments()[0].IsAssignableFrom(device.Peripheral.GetType()))
596                     .OrderBy(x => typeof(NullRegistrationPoint).IsAssignableFrom(x.GetGenericArguments()[1])) // Use the null registration point last
597                     .ToList();
598                 foreach(var connection in connections)
599                 {
600                     IRegistrationPoint regPoint = null;
601                     Type formalType = null;
602                     if(connection.ContainsKey(TYPE_NODE))
603                     {
604                         var name = (string)connection[TYPE_NODE];
605                         formalType = GetDeviceTypeFromName(name);
606                     }
607 
608                     Type foundIface = null;
609                     foreach(var iface in ifaceCandidates)
610                     {
611                         var iRegPoint = iface.GetGenericArguments()[1];
612                         Type objType;
613                         if(formalType != null && iRegPoint.IsAssignableFrom(formalType))
614                         {
615                             objType = formalType;
616                         }
617                         else
618                         {
619                             objType = iRegPoint;
620                         }
621 
622                         object regPointObject;
623                         if(!TryInitializeCtor(objType, connection, out regPointObject))
624                         {
625                             if(connection.Keys.Any() || !TryHandleSingleton(objType, out regPointObject))
626                             {
627                                 continue;
628                             }
629                         }
630                         regPoint = (IRegistrationPoint)regPointObject;
631                         foundIface = iface;
632                         break;
633                         //is a construable type
634                     }
635                     if(foundIface == null)
636                     {
637                         // let's try attachment through the AttachTo mechanism
638                         FailDevice(device.Name, "connection to " + parentName);
639                     }
640                     else
641                     {
642                         Dynamic.InvokeMemberAction(parent, "Register", new object[] {
643                             device.Peripheral,
644                             regPoint
645                         }
646                         );
647                     }
648                 }
649             }
650         }
651 
IsSpecializationOfRawGeneric(Type generic, Type toCheck)652         private static bool IsSpecializationOfRawGeneric(Type generic, Type toCheck)
653         {
654             var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
655             if(generic == cur)
656             {
657                 return true;
658             }
659 
660             return false;
661         }
662 
TryHandleSingleton(Type type, out object instance)663         private static bool TryHandleSingleton(Type type, out object instance)
664         {
665             var properties = type.GetProperties();
666             var desiredProperty = properties.FirstOrDefault(x => x.Name == "Instance" && x.PropertyType == type);
667             if(desiredProperty == null)
668             {
669                 instance = null;
670                 return false;
671             }
672             // We have to use reflection-based approach because of a bug in Mono 5.2:
673             // https://bugzilla.xamarin.com/show_bug.cgi?id=58455
674             //
675             //instance = Dynamic.InvokeGet(InvokeContext.CreateStatic(type), desiredProperty.Name);
676             instance = desiredProperty.GetGetMethod().Invoke(null, Type.EmptyTypes);
677             return true;
678         }
679 
TryInitializeCtor(Type devType, IDictionary<string,dynamic> node, out object constructedObject)680         private bool TryInitializeCtor(Type devType, IDictionary<string,dynamic> node, out object constructedObject)
681         {
682             //Find best suitable constructor, sort parameter list and create instance. Constructor parameters begin with [a-z]
683             var constructors = FindSuitableConstructors(devType, node.Where(x => Char.IsLower(x.Key, 0)).Select(x => x.Key));
684             if(constructors.Count != 1)
685             {
686                 constructedObject = null;
687                 return false;
688             }
689 
690             var ctor = constructors[0];
691             var sortedParams = new Dictionary<string, object>();
692             foreach(var ctorParam in ctor.GetParameters())
693             {
694                 if(node.ContainsKey(ctorParam.Name))
695                 {
696                     if(typeof(IPeripheral).IsAssignableFrom(ctorParam.ParameterType) && !typeof(IMachine).IsAssignableFrom(ctorParam.ParameterType) && !ctorParam.ParameterType.IsArray)
697                     {
698                         var info = deviceList.SingleOrDefault(di => di.Name == node[ctorParam.Name]);
699                         if(info != null)
700                         {
701                             sortedParams.Add(ctorParam.Name, info.Peripheral);
702                         }
703                         else
704                         {
705                             // required peripheral is not yet created, so we need to defer the construction
706                             constructedObject = null;
707                             return true;
708                         }
709                     }
710                     else
711                     {
712                         var temp = GenerateObject(node[ctorParam.Name], ctorParam.ParameterType);
713                         //HACK: The reason of the following line is described at the top of this class.
714                         if(ctorParam.ParameterType.IsPrimitive || ctorParam.ParameterType.IsEnum || Nullable.GetUnderlyingType(ctorParam.ParameterType) != null || temp != null)
715                         {
716                             sortedParams.Add(ctorParam.Name, temp);
717                         }
718                         else
719                         {
720                             // required peripheral is not yet created, so we need to defer the construction
721                             constructedObject = null;
722                             return true;
723                         }
724                     }
725                 }
726                 else if(typeof(IMachine).IsAssignableFrom(ctorParam.ParameterType))
727                 {
728                     sortedParams.Add(ctorParam.Name, machine);
729                 }
730                 else
731                 {
732                     sortedParams.Add(ctorParam.Name, ctorParam.DefaultValue);
733                 }
734             }
735             var paramsArray = sortedParams.Values.ToArray();
736             try
737             {
738                 constructedObject = Dynamic.InvokeConstructor(devType, paramsArray);
739             }
740             catch(ConstructionException)
741             {
742                 constructedObject = null;
743                 return false;
744             }
745             catch(Exception e)
746             {
747                 throw new ConstructionException(String.Format("Could not create object of type {0}.", devType.Name), e);
748             }
749             return true;
750         }
751 
GenerateObject(dynamic value, Type type)752         private dynamic GenerateObject(dynamic value, Type type)
753         {
754             var stringValue = value as string;
755             if(type.IsEnum && stringValue != null)
756             {
757                 object[] parameters = new object[2];
758                 parameters[0] = stringValue;
759                 var parseResult = typeof(Enum).GetMethods().First(x=>x.Name == "TryParse" && x.GetParameters().Length == 2).MakeGenericMethod(type)
760                     .Invoke(null,  parameters);
761                 if((bool)parseResult)
762                 {
763                     return parameters[1];
764                 }
765             }
766             return Dynamic.InvokeConvert(value, type, true);
767         }
768 
GenerateObject(IDictionary<string, dynamic> value, Type type)769         private dynamic GenerateObject(IDictionary<string, dynamic> value, Type type)
770         {
771             object obj;
772             if(!TryInitializeCtor(type, value, out obj))
773             {
774                 throw new ConstructionException("Could not create object " + value);
775             }
776             return obj;
777         }
778 
GenerateObject(IList<dynamic> value, Type type)779         private dynamic GenerateObject(IList<dynamic> value, Type type)
780         {
781             Type innerType = typeof(object);
782             var isArray = type.IsArray;
783             if(type.IsGenericType)
784             {
785                 innerType = type.GetGenericArguments()[0];
786             }
787             else if(isArray)
788             {
789                 innerType = type.GetElementType();
790             }
791 
792             var list = isArray ? Dynamic.InvokeConstructor(typeof(List<>).MakeGenericType(innerType)) : Dynamic.InvokeConstructor(type);
793             foreach(var item in value)
794             {
795                 var obj = GenerateObject(item, innerType);
796                 //HACK: The reason of the following line is described at the top of this class.
797                 if(!innerType.IsPrimitive && !innerType.IsEnum && Nullable.GetUnderlyingType(innerType) == null && obj == null)
798                 {
799                     return null;
800                 }
801                 list.Add(obj);
802             }
803             if(isArray)
804             {
805                 return list.ToArray();
806             }
807             return list;
808         }
809 
FindSuitableConstructors(Type type, IEnumerable<string> parameters)810         public IList<ConstructorInfo> FindSuitableConstructors(Type type, IEnumerable<string> parameters)
811         {
812             var goodCtors = new List<ConstructorInfo>();
813             foreach(var ctor in type.GetConstructors())
814             {
815                 var unusableFound = false;
816                 // every parameter in 'parameters' must be present in the constructor
817                 var ctorParams = ctor.GetParameters();
818                 if(!parameters.All(x => ctorParams.FirstOrDefault(y => y.Name == x) != null))
819                 {
820                     continue;
821                 }
822 
823                 // every argument in ctor must either be present in 'parameters' or set to default
824                 foreach(var param in ctorParams)
825                 {
826                     if(!parameters.Contains(param.Name))
827                     {
828 
829                         if(!param.IsOptional && !typeof(IMachine).IsAssignableFrom(param.ParameterType))
830                         {
831                             unusableFound = true;
832                         }
833                     }
834                 }
835                 if(unusableFound)
836                 {
837                     continue;
838                 }
839                 goodCtors.Add(ctor);
840             }
841             return goodCtors;
842         }
843 
844         private readonly IMachine machine;
845 
846         private const string TYPE_NODE = "_type";
847         private const string IRQ_NODE = "_irq";
848         private const string GPIO_NODE = "_gpio";
849         private const string IRQ_FROM_NODE = "_irqFrom";
850         private const string GPIO_FROM_NODE = "_gpioFrom";
851         private const string CONNECTION_NODE = "_connection";
852     }
853 }
854 
855