Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ModelConsistencyCheckVisitor.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections;
5 using System.Collections.Generic;
6 using System.Linq;
7 
8 using SiliconStudio.Core.Extensions;
9 using SiliconStudio.Core.Reflection;
10 using SiliconStudio.Quantum.References;
11 
12 namespace SiliconStudio.Quantum
13 {
15  {
16  private class ReferenceInfo
17  {
18  public readonly Type ReferenceType;
19  public readonly int EnumerableCount;
20  public ReferenceInfo(Type referenceType, int enumerableCount)
21  {
22  ReferenceType = referenceType;
23  EnumerableCount = enumerableCount;
24  }
25  }
26 
27  private readonly DefaultModelBuilder nodeBuilder;
28  private readonly Stack<ModelNode> contextStack = new Stack<ModelNode>();
29  private readonly Queue<ObjectReference> references = new Queue<ObjectReference>();
30  private readonly List<ModelNode> checkedNodes = new List<ModelNode>();
31  private ModelNode rootNode;
32 
34  {
35  if (nodeBuilder == null) throw new ArgumentNullException("nodeBuilder");
36  this.nodeBuilder = nodeBuilder as DefaultModelBuilder;
37  if (this.nodeBuilder == null) throw new ArgumentException(@"This argument should be a DefaultModelBuilder", "nodeBuilder");
38  }
39 
40  public ICollection<Type> PrimitiveTypes { get { return nodeBuilder.PrimitiveTypes; } }
41 
42  public override void Reset()
43  {
44  contextStack.Clear();
45  rootNode = null;
46  references.Clear();
47  checkedNodes.Clear();
48  base.Reset();
49  }
50 
51  public void Check(ModelNode node, object obj, Type type, bool checkReferences)
52  {
53  Reset();
54 
55  if (node.Content.Value != obj)
56  throw new QuantumConsistencyException("The node content value [{0}]", obj.ToStringSafe(), "The node content value [{0}]", node.Content.Value.ToStringSafe(), node);
57  if (node.Content.Type != type)
58  throw new QuantumConsistencyException("The node content type [{0}]", type.Name, "The node content value [{0}]", node.Content.Type.Name, node);
59 
60  rootNode = node;
61 
62  while (rootNode != null)
63  {
64  if (rootNode.Parent != null)
65  throw new QuantumConsistencyException("A root node", "A node with a parent", rootNode);
66 
67  if (rootNode.Content.Value != null)
68  {
69  var typeDescriptor = TypeDescriptorFactory.Find(rootNode.Content.Type);
70  PushContextNode(rootNode);
71  VisitObject(rootNode.Content.Value, typeDescriptor as ObjectDescriptor, true);
72  PopContextNode();
73  }
74  checkedNodes.Add(rootNode);
75  rootNode = null;
76 
77  if (checkReferences)
78  {
79  while (references.Count > 0)
80  {
81  var reference = references.Dequeue();
82  if (!checkedNodes.Contains(reference.TargetNode))
83  {
84  rootNode = (ModelNode)reference.TargetNode;
85  break;
86  }
87  }
88  }
89  }
90  }
91 
92  /// <inheritdoc/>
93  public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers)
94  {
95  var node = GetContextNode();
96 
97  var referenceInfo = GetReferenceInfo(descriptor.Type, obj);
98  if (node.Content.Reference == null)
99  {
100  if (referenceInfo != null && (node != rootNode || referenceInfo.ReferenceType == typeof(ReferenceEnumerable)))
101  throw new QuantumConsistencyException("Content with a reference", "Content without reference", node);
102 
103  var memberCount = descriptor.Members.Count();
104  if (node.Children.Count != memberCount)
105  throw new QuantumConsistencyException("A node with [{0}] children", memberCount.ToStringSafe(), "A node with [{0}] children", node.Children.Count.ToStringSafe(), node);
106 
107  PushContextNode(node);
108  base.VisitObject(obj, descriptor, true);
109  PopContextNode();
110  }
111  else
112  {
113  if (node.Content.Reference.GetType() != referenceInfo.ReferenceType)
114  throw new QuantumConsistencyException("Content with a [{0}]", referenceInfo.ReferenceType.Name, "Content with a [{0}]", node.Content.Reference.GetType().Name, node);
115  if (node.Content.Value != obj)
116  throw new QuantumConsistencyException("The node content value [{0}]", obj.ToStringSafe(), "The node content value [{0}]", node.Content.Value.ToStringSafe(), node);
117  if (node.Children.Count > 0)
118  throw new QuantumConsistencyException("A node with a reference and no child", null, "A node with a reference and [{0}] children", node.Children.Count.ToStringSafe(), node);
119 
120  AddReference(node, node.Content.Reference);
121  }
122  }
123  /// <inheritdoc/>
124  public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value)
125  {
126  if (!nodeBuilder.NotifyNodeConstructing(containerDescriptor, member))
127  return;
128 
129  var node = GetContextNode();
130  ModelNode child;
131  try
132  {
133  child = (ModelNode)node.Children.Single(x => x.Name == member.Name);
134  }
135  catch (InvalidOperationException)
136  {
137  throw new QuantumConsistencyException("A single child node [{0}]", member.Name, "No child or multiple children [{0}]", member.Name, node);
138  }
139 
140  if (!IsPrimitiveType(child.Content.Type))
141  {
142  PushContextNode(child);
143  Visit(value);
144  PopContextNode();
145  }
146  }
147 
148  /// <inheritdoc/>
149  public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor)
150  {
151  var containerNode = GetContextNode();
152 
153  var count = descriptor.GetCollectionCount(collection);
154  var referenceInfo = GetReferenceInfo(descriptor.Type, collection);
155 
156  if (referenceInfo != null && referenceInfo.EnumerableCount != count)
157  throw new QuantumConsistencyException("A node with an EnumerableReference containing [{0}] items", referenceInfo.EnumerableCount.ToStringSafe(), "A node with an EnumerableReference containing [{0}] items", count.ToStringSafe(), containerNode);
158 
159  if (IsPrimitiveType(descriptor.ElementType, false) || IsEnumerable(descriptor.ElementType))
160  {
161  base.VisitCollection(collection, descriptor);
162  }
163  }
164 
165  /// <inheritdoc/>
166  public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor)
167  {
168  var containerNode = GetContextNode();
169 
170  if (!IsPrimitiveType(descriptor.KeyType))
171  throw new QuantumConsistencyException("A dictionary with a primary type for keys", null, "A dictionary [{0}] for keys", descriptor.KeyType.FullName, containerNode);
172 
173  // TODO: an access to the count function in DictionaryDescriptor
174  var count = ((IEnumerable)dictionary).Cast<object>().Count();
175  var referenceInfo = GetReferenceInfo(descriptor.Type, dictionary);
176  if (referenceInfo != null && referenceInfo.EnumerableCount != count)
177  throw new QuantumConsistencyException("A node with an EnumerableReference containing [{0}] items", referenceInfo.EnumerableCount.ToStringSafe(), "A node with an EnumerableReference containing [{0}] items", count.ToStringSafe(), containerNode);
178 
179  if (IsPrimitiveType(descriptor.ValueType, false) || IsEnumerable(descriptor.ValueType))
180  {
181  base.VisitDictionary(dictionary, descriptor);
182  }
183  }
184 
185  private ReferenceInfo GetReferenceInfo(Type type, object value)
186  {
187  // Is it a reference?
188  if (!type.IsClass || IsPrimitiveType(type))
189  return null;
190 
191  ITypeDescriptor descriptor = value != null ? TypeDescriptorFactory.Find(value.GetType()) : null;
192  var valueType = GetElementValueType(descriptor);
193 
194  // This is either an object reference or a enumerable reference of non-primitive type (excluding custom primitive type)
195  if (valueType == null || !IsPrimitiveType(valueType, false))
196  {
197  var refType = Reference.GetReferenceType(value, Reference.NotInCollection);
198  if (refType == typeof(ReferenceEnumerable))
199  {
200  if (value == null) throw new InvalidOperationException("The value is not expected to be null when its node should contains an ReferenceEnumerable");
201  var enumerable = (IEnumerable)value;
202  return new ReferenceInfo(refType, enumerable.Cast<object>().Count());
203  }
204  return new ReferenceInfo(refType, -1);
205  }
206  return null;
207  }
208 
209  private void AddReference(IModelNode referencer, IReference reference)
210  {
211  var enumerableReference = reference as ReferenceEnumerable;
212  if (enumerableReference != null)
213  {
214  foreach (var itemReference in enumerableReference)
215  {
216  AddObjectReference(referencer, itemReference);
217  }
218  }
219  else
220  {
221  AddObjectReference(referencer, (ObjectReference)reference);
222  }
223  }
224 
225  private void AddObjectReference(IModelNode referencer, ObjectReference reference)
226  {
227  if (reference.TargetNode == null)
228  throw new QuantumConsistencyException("A resolved reference", "An unresolved reference", referencer);
229 
230  if (referencer.Content.Reference == reference && referencer.Content.Value != reference.TargetNode.Content.Value)
231  throw new QuantumConsistencyException("Referenced node with same content value that its referencer", "Referenced node with different content value that its referencer", referencer);
232 
233  if (reference.TargetGuid != reference.TargetNode.Guid)
234  throw new QuantumConsistencyException("Referenced node with same Guid that the reference", "Referenced node with different Guid that the reference", referencer);
235 
236  references.Enqueue(reference);
237  }
238 
239  private void PushContextNode(ModelNode node)
240  {
241  contextStack.Push(node);
242  }
243 
244  private void PopContextNode()
245  {
246  contextStack.Pop();
247  }
248 
249  private ModelNode GetContextNode()
250  {
251  return contextStack.Peek();
252  }
253 
254  private static bool IsEnumerable(Type type)
255  {
256  return typeof(IEnumerable).IsAssignableFrom(type);
257  }
258 
259  private bool IsPrimitiveType(Type type, bool includeAdditionalPrimitiveTypes = true)
260  {
261  if (type == null)
262  return false;
263 
264  return type.IsPrimitive || type == typeof(string) || type.IsEnum || (includeAdditionalPrimitiveTypes && PrimitiveTypes.Any(x => x.IsAssignableFrom(type)));
265  }
266 
267  private static Type GetElementValueType(ITypeDescriptor descriptor)
268  {
269  var dictionaryDescriptor = descriptor as DictionaryDescriptor;
270  var collectionDescriptor = descriptor as CollectionDescriptor;
271  if (dictionaryDescriptor != null)
272  {
273  return dictionaryDescriptor.ValueType;
274  }
275  return collectionDescriptor != null ? collectionDescriptor.ElementType : null;
276  }
277 
278  }
279 }
Provides a descriptor for a System.Collections.ICollection.
Type ElementType
Gets or sets the type of the element.
void Check(ModelNode node, object obj, Type type, bool checkReferences)
override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers)
Visits an object (either a class or a struct) The object.The descriptor.
This class is the default implementation of the IModelNode.
Definition: ModelNode.cs:14
Default implementation of a ITypeDescriptor.
A visitor for serializable data (binary, yaml and editor).
IContent Content
Gets the content of the IModelNode.
Definition: IModelNode.cs:41
A class representing an enumeration of references to multiple objects.
override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor)
Visits a dictionary. The dictionary.The descriptor.
Provides a descriptor for a System.Collections.IDictionary.
IReadOnlyCollection< IModelNode > Children
Definition: ModelNode.cs:50
_In_ size_t count
Definition: DirectXTexP.h:174
This interface provides objects and methods to build a nodal view model from a given object...
Definition: INodeBuilder.cs:14
override void Reset()
Resets this instance (clears the cache of visited objects).
The type of the serialized type will be passed as a generic arguments of the serializer. Example: serializer of A becomes instantiated as Serializer{A}.
virtual IContent Content
Definition: ModelNode.cs:44
An exception that occurs during consistency checks of Quantum objects, indicating that a IModelNode i...
IModelNode TargetNode
Gets the model node targeted by this reference, if available.
Provides access members of a type.
override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor)
Visits a collection. The collection.The descriptor.
A class representing a reference to another object that has a different model.
override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value)
Visits an object member. The container.The container descriptor.The member.The value.
Guid Guid
Gets or sets the System.Guid.
Definition: IModelNode.cs:26
IReferencable.AddReference() event.
Guid TargetGuid
Gets the Guid of the model node targeted by this reference, if available.