Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
DefaultModelBuilder.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;
11 using SiliconStudio.Quantum.Contents;
12 using SiliconStudio.Quantum.References;
13 
14 namespace SiliconStudio.Quantum
15 {
16  /// <summary>
17  /// The default <see cref="INodeBuilder"/> implementation that construct a model from a data object.
18  /// </summary>
19  internal class DefaultModelBuilder : DataVisitorBase, INodeBuilder
20  {
21  private readonly Stack<ModelNode> contextStack = new Stack<ModelNode>();
22  private readonly HashSet<IContent> referenceContents = new HashSet<IContent>();
23  private ModelNode rootNode;
24  private Guid rootGuid;
25 
26  /// <summary>
27  /// Initializes a new instance of the <see cref="DefaultModelBuilder"/> class that can be used to construct a model for a data object.
28  /// </summary>
29  public DefaultModelBuilder()
30  {
31  PrimitiveTypes = new List<Type>();
32  AvailableCommands = new List<INodeCommand>();
33  }
34 
35  /// <inheritdoc/>
36  public ICollection<Type> PrimitiveTypes { get; private set; }
37 
38  /// <inheritdoc/>
39  public ICollection<INodeCommand> AvailableCommands { get; private set; }
40 
41  /// <inheritdoc/>
42  public IEnumerable<IContent> ReferenceContents { get { return referenceContents; } }
43 
44  /// <inheritdoc/>
45  public event EventHandler<NodeConstructingArgs> NodeConstructing;
46 
47  /// <inheritdoc/>
48  public event EventHandler<NodeConstructedArgs> NodeConstructed;
49 
50  /// <summary>
51  /// Reset the visitor in order to use it to generate another model.
52  /// </summary>
53  public override void Reset()
54  {
55  rootNode = null;
56  rootGuid = Guid.Empty;
57  contextStack.Clear();
58  referenceContents.Clear();
59  base.Reset();
60  }
61 
62  /// <inheritdoc/>
63  public IModelNode Build(object obj, Type type, Guid guid)
64  {
65  Reset();
66  rootGuid = guid;
67  var typeDescriptor = TypeDescriptorFactory.Find(obj != null ? obj.GetType() : type);
68  VisitObject(obj, typeDescriptor as ObjectDescriptor, true);
69 
70  return rootNode;
71  }
72 
73  /// <inheritdoc/>
74  public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers)
75  {
76  bool isRootNode = contextStack.Count == 0;
77  if (isRootNode)
78  {
79  if (!NotifyNodeConstructing(descriptor))
80  return;
81 
82  // If we are in the case of a collection of collections, we might have a root node that is actually an enumerable reference
83  // This would be the case for each collection within the base collection.
84  IReference reference = CreateReferenceForNode(descriptor.Type, obj);
85  reference = reference is ReferenceEnumerable ? reference : null;
86  IContent content = descriptor.Type.IsStruct() ? new BoxedContent(obj, descriptor, IsPrimitiveType(descriptor.Type)) : new ObjectContent(obj, descriptor, IsPrimitiveType(descriptor.Type), reference);
87  rootNode = new ModelNode(descriptor.Type.Name, content, rootGuid);
88  if (reference != null && descriptor.Type.IsStruct())
89  throw new QuantumConsistencyException("A collection type", "A structure type", rootNode);
90 
91  if (reference != null)
92  referenceContents.Add(content);
93 
94  AvailableCommands.Where(x => x.CanAttach(rootNode.Content.Descriptor, null)).ForEach(rootNode.AddCommand);
95  NotifyNodeConstructed(content);
96 
97  if (obj == null)
98  {
99  rootNode.Seal();
100  return;
101  }
102  PushContextNode(rootNode);
103  }
104 
105  if (!IsPrimitiveType(descriptor.Type))
106  {
107  base.VisitObject(obj, descriptor, true);
108  }
109 
110  if (isRootNode)
111  {
112  PopContextNode();
113  rootNode.Seal();
114  }
115  }
116 
117  /// <inheritdoc/>
118  public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor)
119  {
120  // Don't visit items unless they are primitive or enumerable (collections within collections)
121  if (IsPrimitiveType(descriptor.ElementType, false) || IsEnumerable(descriptor.ElementType))
122  {
123  base.VisitCollection(collection, descriptor);
124  }
125  }
126 
127  /// <inheritdoc/>
128  public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor)
129  {
130  if (!IsPrimitiveType(descriptor.KeyType))
131  throw new InvalidOperationException("The type of dictionary key must be a primary type.");
132 
133  // Don't visit items unless they are primitive or enumerable (collections within collections)
134  if (IsPrimitiveType(descriptor.ValueType, false) || IsEnumerable(descriptor.ValueType))
135  {
136  base.VisitDictionary(dictionary, descriptor);
137  }
138  }
139 
140  /// <summary>
141  /// Raises the <see cref="NodeConstructing"/> event.
142  /// </summary>
143  /// <param name="descriptor">The descriptor of the root object being constructed.</param>
144  /// <returns><c>true</c> if the node should be constructed, <c>false</c> if it should be discarded.</returns>
145  /// <remarks>This method is internal so it can be used by the <see cref="ModelConsistencyCheckVisitor"/>.</remarks>
146  internal bool NotifyNodeConstructing(ObjectDescriptor descriptor)
147  {
148  var handler = NodeConstructing;
149  if (handler != null)
150  {
151  var args = new NodeConstructingArgs(descriptor, null);
152  handler(this, args);
153  return !args.Discard;
154  }
155  return true;
156  }
157 
158  /// <summary>
159  /// Raises the <see cref="NodeConstructing"/> event.
160  /// </summary>
161  /// <param name="containerDescriptor">The descriptor of the container of the member being constructed, or of the object itself it is a root object.</param>
162  /// <param name="member">The member descriptor of the member being constructed.</param>
163  /// <returns><c>true</c> if the node should be constructed, <c>false</c> if it should be discarded.</returns>
164  /// <remarks>This method is internal so it can be used by the <see cref="ModelConsistencyCheckVisitor"/>.</remarks>
165  internal bool NotifyNodeConstructing(ObjectDescriptor containerDescriptor, IMemberDescriptor member)
166  {
167  var handler = NodeConstructing;
168  if (handler != null)
169  {
170  var args = new NodeConstructingArgs(containerDescriptor, (MemberDescriptorBase)member);
171  handler(this, args);
172  return !args.Discard;
173  }
174  return true;
175  }
176 
177  /// <summary>
178  /// Raises the <see cref="NodeConstructed"/> event.
179  /// </summary>
180  /// <param name="content">The content of the node that has been constructed.</param>
181  /// <remarks>This method is internal so it can be used by the <see cref="ModelConsistencyCheckVisitor"/>.</remarks>
182  internal void NotifyNodeConstructed(IContent content)
183  {
184  var handler = NodeConstructed;
185  if (handler != null)
186  {
187  var args = new NodeConstructedArgs(content);
188  handler(this, args);
189  }
190  }
191 
192  /// <inheritdoc/>
193  public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value)
194  {
195  if (!NotifyNodeConstructing(containerDescriptor, member))
196  return;
197 
198  // If this member should contains a reference, create it now.
199  IReference reference = CreateReferenceForNode(member.Type, value);
200  ModelNode containerNode = GetContextNode();
201  ITypeDescriptor typeDescriptor = TypeDescriptorFactory.Find(member.Type);
202  IContent content = new MemberContent(containerNode.Content, member, typeDescriptor, IsPrimitiveType(member.Type), reference);
203  var node = new ModelNode(member.Name, content, Guid.NewGuid());
204  containerNode.AddChild(node);
205 
206  if (reference != null)
207  referenceContents.Add(content);
208 
209  if (!(reference is ObjectReference))
210  {
211  // For enumerable references, we visit the member to allow VisitCollection or VisitDictionary to enrich correctly the node.
212  PushContextNode(node);
213  Visit(value);
214  PopContextNode();
215  }
216 
217  AvailableCommands.Where(x => x.CanAttach(node.Content.Descriptor, (MemberDescriptorBase)member)).ForEach(node.AddCommand);
218  NotifyNodeConstructed(content);
219 
220  node.Seal();
221  }
222 
223  private IReference CreateReferenceForNode(Type type, object value)
224  {
225  // Is it a reference?
226  if ((!type.IsClass && !type.IsInterface) || IsPrimitiveType(type))
227  return null;
228 
229  ITypeDescriptor descriptor = value != null ? TypeDescriptorFactory.Find(value.GetType()) : null;
230  var valueType = GetElementValueType(descriptor);
231 
232  // This is either an object reference or a enumerable reference of non-primitive type (excluding custom primitive type)
233  if (valueType == null || !IsPrimitiveType(valueType, false))
234  return Reference.CreateReference(value, type, Reference.NotInCollection);
235 
236  return null;
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  if (type.IsNullable())
265  type = Nullable.GetUnderlyingType(type);
266 
267  return type.IsPrimitive || type == typeof(string) || type.IsEnum || (includeAdditionalPrimitiveTypes && PrimitiveTypes.Any(x => x.IsAssignableFrom(type)));
268  }
269 
270  private static Type GetElementValueType(ITypeDescriptor descriptor)
271  {
272  var dictionaryDescriptor = descriptor as DictionaryDescriptor;
273  var collectionDescriptor = descriptor as CollectionDescriptor;
274  if (dictionaryDescriptor != null)
275  {
276  return dictionaryDescriptor.ValueType;
277  }
278  return collectionDescriptor != null ? collectionDescriptor.ElementType : null;
279  }
280  }
281 }
Provides a descriptor for a System.Collections.ICollection.
Type ElementType
Gets or sets the type of the element.
Default implementation of a ITypeDescriptor.
A visitor for serializable data (binary, yaml and editor).
Content of a IModelNode.
Definition: IContent.cs:13
A class representing an enumeration of references to multiple objects.
An implementation of IContent that gives access to an object or a boxed struct.
Provides a descriptor for a System.Collections.IDictionary.
An implementation of IContent that gives access to a member of an object.
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}.
Provides access members of a type.
Base class for IMemberDescriptor for a MemberInfo
The device failed due to a badly formed command. This is a run-time issue; The application should des...
A class representing a reference to another object that has a different model.