2 // Copyright (C) 2010 Novell Inc. http://novell.com
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 using System.Collections.Generic;
26 using System.Reflection;
27 using System.Windows.Markup;
28 using System.Xaml.Schema;
32 public class XamlObjectWriter : XamlWriter, IXamlLineInfoConsumer
34 public XamlObjectWriter (XamlSchemaContext schemaContext)
35 : this (schemaContext, null)
39 public XamlObjectWriter (XamlSchemaContext schemaContext, XamlObjectWriterSettings settings)
41 if (schemaContext == null)
42 throw new ArgumentNullException ("schemaContext");
43 this.sctx = schemaContext;
44 this.settings = settings ?? new XamlObjectWriterSettings ();
46 var p = new PrefixLookup (sctx);
47 service_provider = new ValueSerializerContext (p, sctx);
48 namespaces = p.Namespaces;
51 XamlSchemaContext sctx;
52 XamlObjectWriterSettings settings;
54 XamlWriterStateManager manager = new XamlWriterStateManager<XamlObjectWriterException, XamlObjectWriterException> (false);
56 int line = -1, column = -1;
57 Stack<XamlMember> members = new Stack<XamlMember> ();
59 List<NamespaceDeclaration> namespaces;
60 IValueSerializerContext service_provider;
61 Stack<ObjectState> object_states = new Stack<ObjectState> ();
67 public object KeyValue;
68 public List<object> Contents = new List<object> ();
69 public List<XamlMember> WrittenProperties = new List<XamlMember> ();
70 public bool IsInstantiated;
71 public bool IsGetObject;
73 public string FactoryMethod;
74 public List<object> Arguments = new List<object> ();
77 public virtual object Result {
78 get { return result; }
81 public INameScope RootNameScope {
82 get { throw new NotImplementedException (); }
85 public override XamlSchemaContext SchemaContext {
89 public bool ShouldProvideLineInfo {
90 get { return line > 0 && column > 0; }
95 throw new NotImplementedException ();
98 protected override void Dispose (bool disposing)
103 while (object_states.Count > 0) {
104 if (object_states.Count == members.Count)
110 protected virtual void OnAfterBeginInit (object value)
112 throw new NotImplementedException ();
115 protected virtual void OnAfterEndInit (object value)
117 throw new NotImplementedException ();
120 protected virtual void OnAfterProperties (object value)
122 throw new NotImplementedException ();
125 protected virtual void OnBeforeProperties (object value)
127 throw new NotImplementedException ();
130 protected virtual bool OnSetValue (object eventSender, XamlMember member, object value)
132 if (settings.XamlSetValueHandler != null) {
133 settings.XamlSetValueHandler (eventSender, new XamlSetValueEventArgs (member, value));
139 void SetValue (XamlMember member, object value)
141 if (member.IsDirective)
143 if (!OnSetValue (this, member, value))
144 member.Invoker.SetValue (object_states.Peek ().Value, value);
147 public void SetLineInfo (int lineNumber, int linePosition)
150 column = linePosition;
153 static readonly BindingFlags static_flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
155 [MonoTODO ("Dictionary needs implementation")]
156 public override void WriteEndMember ()
158 manager.EndMember ();
160 var xm = members.Pop ();
161 var state = object_states.Peek ();
162 var contents = state.Contents;
164 if (xm == XamlLanguage.PositionalParameters) {
165 manager.AcceptMultipleValues = false;
166 //state.PositionalParameterIndex = -1;
167 FillConstructedObject (true);
168 } else if (xm == XamlLanguage.FactoryMethod) {
169 if (contents.Count != 1 || !(contents [0] is string))
170 throw new XamlObjectWriterException (String.Format ("FactoryMethod must be non-empty string name. {0} value exists.", contents.Count > 0 ? contents [0] : "0"));
171 state.FactoryMethod = (string) contents [0];
172 } else if (xm == XamlLanguage.Arguments) {
173 if (state.FactoryMethod != null) {
174 var mi = state.Type.UnderlyingType.GetMethods (static_flags).FirstOrDefault (mii => mii.Name == state.FactoryMethod && mii.GetParameters ().Length == contents.Count);
176 throw new XamlObjectWriterException (String.Format ("Specified static factory method '{0}' for type '{1}' was not found", state.FactoryMethod, state.Type));
177 state.Value = mi.Invoke (null, contents.ToArray ());
180 FillConstructedObject (false);
181 } else if (xm == XamlLanguage.Initialization) {
182 // ... and no need to do anything. The object value to pop *is* the return value.
183 } else if (xm == XamlLanguage.Items) {
184 var coll = state.Value;
185 foreach (var content in contents)
186 xm.Type.Invoker.AddToCollection (coll, content);
187 } else if (xm.Type.IsDictionary) {
188 throw new NotImplementedException ();
190 if (contents.Count > 1)
191 throw new XamlDuplicateMemberException (String.Format ("Property '{0}' is already set to this '{1}' object", xm, state.Type));
192 if (contents.Count == 1) {
193 var value = contents [0];
194 if (!xm.Type.IsCollection || !xm.IsReadOnly) // exclude read-only object.
195 SetValue (xm, value);
201 if (object_states.Count > 0)
202 object_states.Peek ().WrittenProperties.Add (xm);
203 //written_properties_stack.Peek ().Add (xm);
206 void FillConstructedObject (bool considerPositionalParameters)
208 var state = object_states.Peek ();
210 var args = state.Type.GetSortedConstructorArguments ();
211 var argt = args != null ? (IList<XamlType>) (from arg in args select arg.Type).ToArray () : considerPositionalParameters ? state.Type.GetPositionalParameters (state.Contents.Count) : null;
213 var argv = new object [argt.Count];
214 for (int i = 0; i < argv.Length; i++)
215 argv [i] = GetCorrectlyTypedValue (argt [i], state.Contents [i]);
216 state.Value = state.Type.Invoker.CreateInstance (argv);
217 state.IsInstantiated = true;
220 object GetCorrectlyTypedValue (XamlType xt, object value)
223 return DoGetCorrectlyTypedValue (xt, value);
224 } catch (Exception ex) {
225 // For + ex.Message, the runtime should print InnerException message like .NET does.
226 throw new XamlObjectWriterException (String.Format ("Could not convert object \'{0}' (of type {1}) to {2}: ", value, value != null ? (object) value.GetType () : "(null)", xt) + ex.Message, ex);
230 // It expects that it is not invoked when there is no value to
232 // When it is passed null, then it returns a default instance.
233 // For example, passing null as Int32 results in 0.
234 object DoGetCorrectlyTypedValue (XamlType xt, object value)
237 return xt.Invoker.CreateInstance (new object [0]);
239 // FIXME: this could be generalized by some means, but I cannot find any.
240 if (xt.UnderlyingType == typeof (Type))
241 xt = XamlLanguage.Type;
242 if (xt == XamlLanguage.Type && value is string)
243 value = new TypeExtension ((string) value);
245 // FIXME: this could be generalized by some means, but I cannot find any.
246 if (xt.UnderlyingType == typeof (XamlType) && value is string) {
247 var nsr = (IXamlNamespaceResolver) service_provider.GetService (typeof (IXamlNamespaceResolver));
248 value = sctx.GetXamlType (XamlTypeName.Parse ((string) value, nsr));
251 if (value is MarkupExtension)
252 value = ((MarkupExtension) value).ProvideValue (service_provider);
254 if (IsAllowedType (xt, value))
257 if (xt.TypeConverter != null && value != null) {
258 var tc = xt.TypeConverter.ConverterInstance;
259 if (tc != null && tc.CanConvertFrom (value.GetType ()))
260 value = tc.ConvertFrom (value);
264 throw new XamlObjectWriterException (String.Format ("Value '{1}' (of type {2}) is not of or convertible to type {0}", xt, value, value != null ? (object) value.GetType () : "(null)"));
267 bool IsAllowedType (XamlType xt, object value)
270 xt.UnderlyingType == null ||
271 xt.UnderlyingType.IsInstanceOfType (value) ||
272 value == null && xt == XamlLanguage.Null ||
273 xt.IsMarkupExtension && IsAllowedType (xt.MarkupExtensionReturnType, value);
276 public override void WriteEndObject ()
278 manager.EndObject (object_states.Count > 0);
280 InitializeObjectIfRequired (false); // this is required for such case that there was no StartMember call.
282 var state = object_states.Pop ();
283 var obj = GetCorrectlyTypedValue (state.Type, state.Value);
284 if (members.Count > 0) {
285 var pstate = object_states.Peek ();
286 pstate.Contents.Add (obj);
287 pstate.WrittenProperties.Add (members.Peek ());
289 if (object_states.Count == 0)
293 public override void WriteGetObject ()
295 manager.GetObject ();
297 var xm = members.Peek ();
298 // see GetObjectOnNonNullString() test. Below is invalid.
299 //if (!xm.Type.IsCollection)
300 // throw new XamlObjectWriterException (String.Format ("WriteGetObject method can be invoked only when current member '{0}' is of collection type", xm.Name));
302 var instance = xm.Invoker.GetValue (object_states.Peek ().Value);
303 if (instance == null)
304 throw new XamlObjectWriterException (String.Format ("The value for '{0}' property is null", xm.Name));
306 var state = new ObjectState () {Type = SchemaContext.GetXamlType (instance.GetType ()), Value = instance, IsInstantiated = true, IsGetObject = true};
307 object_states.Push (state);
310 public override void WriteNamespace (NamespaceDeclaration namespaceDeclaration)
312 if (namespaceDeclaration == null)
313 throw new ArgumentNullException ("namespaceDeclaration");
315 manager.Namespace ();
317 namespaces.Add (namespaceDeclaration);
320 public override void WriteStartMember (XamlMember property)
322 if (property == null)
323 throw new ArgumentNullException ("property");
325 manager.StartMember ();
326 if (property == XamlLanguage.PositionalParameters)
327 manager.AcceptMultipleValues = true;
329 // FIXME: this condition needs to be examined. What is known to be prevented are: PositionalParameters, Initialization and Base (the last one sort of indicates there's a lot more).
330 if (!(property is XamlDirective))
331 InitializeObjectIfRequired (false);
333 //var wpl = object_states.Peek ().WrittenProperties;
334 // FIXME: enable this. Duplicate property check should
335 // be differentiate from duplicate contents (both result
336 // in XamlDuplicateMemberException though).
337 // Now it is done at WriteStartObject/WriteValue, but
338 // it is simply wrong.
339 // if (wpl.Contains (property))
340 // throw new XamlDuplicateMemberException (String.Format ("Property '{0}' is already set to this '{1}' object", property, object_states.Peek ().Type));
341 // wpl.Add (property);
343 members.Push (property);
346 void InitializeObjectIfRequired (bool waitForParameters)
348 var state = object_states.Peek ();
349 if (state.IsInstantiated)
352 if (waitForParameters && (state.Type.ConstructionRequiresArguments || state.Type.HasPositionalParameters (service_provider)))
355 // FIXME: "The default techniques in absence of a factory method are to attempt to find a default constructor, then attempt to find an identified type converter on type, member, or destination type."
356 // http://msdn.microsoft.com/en-us/library/system.xaml.xamllanguage.factorymethod%28VS.100%29.aspx
358 if (state.FactoryMethod != null) // FIXME: it must be implemented and verified with tests.
359 throw new NotImplementedException ();
361 obj = state.Type.Invoker.CreateInstance (null);
363 state.IsInstantiated = true;
366 public override void WriteStartObject (XamlType xamlType)
368 if (xamlType == null)
369 throw new ArgumentNullException ("xamlType");
371 manager.StartObject ();
373 var xm = members.Count > 0 ? members.Peek () : null;
374 var pstate = xm != null ? object_states.Peek () : null;
375 var wpl = xm == null || xm.Type.IsCollection || xm.Type.IsDictionary ? null : pstate.WrittenProperties;
376 if (wpl != null && wpl.Contains (xm))
377 throw new XamlDuplicateMemberException (String.Format ("Property '{0}' is already set to this '{1}' object", xm, pstate.Type));
379 var cstate = new ObjectState () {Type = xamlType, IsInstantiated = false};
380 object_states.Push (cstate);
382 if (!xamlType.IsContentValue (service_provider))
383 InitializeObjectIfRequired (true);
385 if (wpl != null) // note that this adds to the *owner* object's properties.
389 public override void WriteValue (object value)
393 var xm = members.Peek ();
394 var state = object_states.Peek ();
396 var wpl = xm != null && xm != XamlLanguage.Items ? state.WrittenProperties : null;
397 if (wpl != null && wpl.Contains (xm))
398 throw new XamlDuplicateMemberException (String.Format ("Property '{0}' is already set to this '{1}' object", xm, state.Type));
400 if (xm == XamlLanguage.Initialization) {
401 value = GetCorrectlyTypedValue (state.Type, value);
403 state.IsInstantiated = true;
405 else if (xm.Type.IsDictionary) {
406 if (xm == XamlLanguage.Key)
407 state.KeyValue = GetCorrectlyTypedValue (xm.Type.KeyType, value);
409 state.Contents.Add (GetCorrectlyTypedValue (xm.Type.ItemType, value));
411 else if (xm.Type.IsCollection)
412 state.Contents.Add (GetCorrectlyTypedValue (xm.Type.ItemType, value));
414 state.Contents.Add (GetCorrectlyTypedValue (xm.Type, value));