2005-08-23 Iain McCoy <iain@mccoy.id.au>
[mono.git] / mcs / class / PresentationFramework / Mono.Windows.Serialization / CodeWriter.cs
1 //
2 // CodeWriter.cs
3 //
4 // Author:
5 //   Iain McCoy (iain@mccoy.id.au)
6 //
7 // (C) 2005 Iain McCoy
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Diagnostics;
31 using System.Reflection;
32 using System.IO;
33 using System.Xml;
34 using System.Collections;
35 using System.CodeDom;
36 using System.CodeDom.Compiler;
37 using System.Windows.Serialization;
38 using System.Windows;
39
40 namespace Mono.Windows.Serialization {
41         public class CodeWriter {
42                 TextWriter writer;
43                 ICodeGenerator generator;
44                 bool isPartial;
45                 
46                 ArrayList objects = new ArrayList();
47                 Hashtable nameClashes = new Hashtable();
48                 int tempIndex = 0;
49
50                 CodeCompileUnit code;
51                 CodeTypeDeclaration type;
52                 CodeConstructor constructor;
53
54                 public static string Parse(XmlTextReader reader, ICodeGenerator generator,  bool isPartial)
55                 {
56                         CodeWriter cw = new CodeWriter(reader, generator, isPartial);
57                         return ((StringWriter)cw.writer).ToString();
58                 }
59
60                 // pushes: the code writer
61                 private void init(ICodeGenerator generator, bool isPartial)
62                 {
63                         this.generator = generator;
64                         this.isPartial = isPartial;
65                         this.writer = new StringWriter();
66                         code = new CodeCompileUnit();
67                         push(code);
68                 }
69
70                 
71                 private CodeWriter(XmlTextReader reader, ICodeGenerator generator, bool isPartial)
72                 {
73                         init(generator, isPartial);
74                         XamlParser p = new XamlParser(reader);
75                         XamlNode n;
76                         while (true) {
77                                 n = p.GetNextNode();
78                                 if (n == null)
79                                         break;
80                                 Debug.WriteLine("CodeWriter: INCOMING " + n.GetType());
81                                 if (n is XamlDocumentStartNode) {
82                                         Debug.WriteLine("CodeWriter: document begins");
83                                         // do nothing
84                                 } else if (n is XamlElementStartNode && n.Depth == 0) {
85                                         Debug.WriteLine("CodeWriter: element begins as top-level");
86                                         CreateTopLevel(((XamlElementStartNode)n).ElementType, ((XamlElementStartNode)n).name);
87                                 } else if (n is XamlElementStartNode && ((XamlElementStartNode)n).propertyObject) {
88                                         Debug.WriteLine("CodeWriter: element begins as property value");
89                                         CreatePropertyObject(((XamlElementStartNode)n).ElementType, ((XamlElementStartNode)n).name);
90                                 } else if (n is XamlElementStartNode) {
91                                         Debug.WriteLine("CodeWriter: element begins");
92                                         CreateObject(((XamlElementStartNode)n).ElementType, ((XamlElementStartNode)n).name);
93                                 } else if (n is XamlPropertyNode && ((XamlPropertyNode)n).PropInfo != null) {
94                                         Debug.WriteLine("CodeWriter: normal property begins");
95                                         CreateProperty(((XamlPropertyNode)n).PropInfo);
96                                 } else if (n is XamlPropertyNode && ((XamlPropertyNode)n).DP != null) {
97                                         Debug.WriteLine("CodeWriter: dependency property begins");
98                                         DependencyProperty dp = ((XamlPropertyNode)n).DP;
99                                         Type typeAttachedTo = dp.OwnerType;
100                                         string propertyName = ((XamlPropertyNode)n).PropertyName;
101                                         
102                                         CreateDependencyProperty(typeAttachedTo, propertyName, dp.PropertyType);
103                                 } else if (n is XamlClrEventNode && !(((XamlClrEventNode)n).EventMember is EventInfo)) {
104                                         Debug.WriteLine("CodeWriter: delegate property");
105                                         CreatePropertyDelegate(((XamlClrEventNode)n).Value, ((PropertyInfo)((XamlClrEventNode)n).EventMember).PropertyType);
106                                         EndProperty();
107
108
109                                 } else if (n is XamlClrEventNode) {
110                                         Debug.WriteLine("CodeWriter: event");
111                                         CreateEvent((EventInfo)((XamlClrEventNode)n).EventMember);
112                                         CreateEventDelegate(((XamlClrEventNode)n).Value, ((EventInfo)((XamlClrEventNode)n).EventMember).EventHandlerType);
113                                         EndEvent();
114
115                                 } else if (n is XamlTextNode && ((XamlTextNode)n).mode == XamlParseMode.Object){
116                                         Debug.WriteLine("CodeWriter: text for object");
117                                         CreateObjectText(((XamlTextNode)n).TextContent);
118                                 } else if (n is XamlTextNode && ((XamlTextNode)n).mode == XamlParseMode.Property){
119                                         Debug.WriteLine("CodeWriter: text for property");
120                                         CreatePropertyText(((XamlTextNode)n).TextContent, ((XamlTextNode)n).finalType);
121                                         EndProperty();
122                                 } else if (n is XamlTextNode && ((XamlTextNode)n).mode == XamlParseMode.DependencyProperty){
123                                         Debug.WriteLine("CodeWriter: text for dependency property");
124                                         CreateDependencyPropertyText(((XamlTextNode)n).TextContent, ((XamlTextNode)n).finalType);
125                                         EndDependencyProperty();
126                                 } else if (n is XamlPropertyComplexEndNode) {
127                                         Debug.WriteLine("CodeWriter: end complex property");
128                                         Debug.WriteLine("CodeWriter: final type is " + ((XamlPropertyComplexEndNode)n).finalType);
129                                         EndPropertyObject(((XamlPropertyComplexEndNode)n).finalType);
130                                         EndProperty();
131                                 } else if (n is XamlLiteralContentNode) {
132                                         Debug.WriteLine("CodeWriter: literal content");
133                                         CreateCode(((XamlLiteralContentNode)n).Content);
134                                 } else if (n is XamlElementEndNode) {
135                                         Debug.WriteLine("CodeWriter: end element");
136                                         if (!((XamlElementEndNode)n).propertyObject)
137                                                 EndObject();
138                                 } else if (n is XamlDocumentEndNode) {
139                                         Debug.WriteLine("CodeWriter: end document");
140                                         Finish();
141                                 } else {
142                                         throw new Exception("Unknown node " + n.GetType());
143                                 }
144                         }
145
146                 }
147                         
148         
149                 // pushes: a CodeVariableReferenceExpression to the present
150                 //      instance
151                 public void CreateTopLevel(Type parent, string className)
152                 {
153                         debug();
154                         if (className == null) {
155                                 className = "derived" + parent.Name;
156                         }
157                         int endNamespaceName = className.LastIndexOf(".");
158                         string clrNamespace;
159                         if (endNamespaceName < 0) {
160                                 clrNamespace = "DefaultNamespace";
161                         } else {
162                                 clrNamespace = className.Substring(0,
163                                                 endNamespaceName);
164                                 className = className.Substring(endNamespaceName+1);
165                         }
166                         CodeNamespace ns = new CodeNamespace(clrNamespace);
167                         ((CodeCompileUnit)objects[0]).Namespaces.Add(ns);
168
169                         type = new CodeTypeDeclaration(className);
170                         if (isPartial) {
171 #if NET_2_0
172                                 type.IsPartial = isPartial;
173 #else
174                                 throw new Exception("Cannot create partial class");
175 #endif
176                         }
177                         type.BaseTypes.Add(new CodeTypeReference(parent));
178                         constructor = new CodeConstructor();
179                         type.Members.Add(constructor);
180                         ns.Types.Add(type);
181                         
182                         push(new CodeThisReferenceExpression());
183                 }
184
185                 // bottom of stack holds CodeVariableReferenceExpression
186                 // pushes a reference to the new current type
187                 public void CreateObject(Type type, string varName)
188                 {
189                         debug();
190                         bool isDefaultName;
191                         if (varName == null) {
192                                 isDefaultName = true;
193                                 varName = Char.ToLower(type.Name[0]) + type.Name.Substring(1);
194                                 // make sure something sensible happens when class
195                                 // names start with a lowercase letter
196                                 if (varName == type.Name)
197                                         varName = "_" + varName;
198                         } else {
199                                 isDefaultName = false;
200                         }
201
202                         if (isDefaultName) {
203                                 if (!nameClashes.ContainsKey(varName))
204                                         nameClashes[varName] = 0;
205
206                                 nameClashes[varName] = 1 + (int)nameClashes[varName];
207                                 varName += (int)nameClashes[varName];
208                         }
209
210
211                         if (isDefaultName) {
212                                 CodeVariableDeclarationStatement declaration = 
213                                                 new CodeVariableDeclarationStatement(type, 
214                                                                 varName,
215                                                                 new CodeObjectCreateExpression(type));
216                                 constructor.Statements.Add(declaration);
217                         } else {
218                                 CodeMemberField declaration = new CodeMemberField(type, varName);
219                                 declaration.InitExpression = new CodeObjectCreateExpression(type);
220                                 this.type.Members.Add(declaration);
221                         }
222                         CodeVariableReferenceExpression varRef = new CodeVariableReferenceExpression(varName);
223                         CodeMethodInvokeExpression addChild = new CodeMethodInvokeExpression(
224                                         (CodeExpression)peek(),
225                                         "AddChild",
226                                         varRef);
227                         constructor.Statements.Add(addChild);
228                         push(varRef);
229                 }
230
231                 // top of stack is a reference to an object
232                 // pushes a reference to the property
233                 public void CreateProperty(PropertyInfo property)
234                 {
235                         debug();
236                         CodePropertyReferenceExpression prop = new CodePropertyReferenceExpression(
237                                         (CodeExpression)peek(),
238                                         property.Name);
239                         push(prop);
240                 }
241
242                 // top of stack is a reference to an object
243                 // pushes a reference to the event
244                 public void CreateEvent(EventInfo evt)
245                 {
246                         debug();
247                         CodeEventReferenceExpression expr = new CodeEventReferenceExpression(
248                                         (CodeExpression)peek(),
249                                         evt.Name);
250                         push(expr);
251                 }
252
253                 // top of stack is a reference to an object
254                 // pushes a reference to the expression that
255                 // will set the property and a reference to
256                 // the name of the temp variable to hold the
257                 // property
258                 public void CreateDependencyProperty(Type attachedTo, string propertyName, Type propertyType)
259                 {
260                         debug();
261                         string varName = "temp";
262                         varName += tempIndex;
263                         tempIndex += 1;
264                         CodeVariableDeclarationStatement decl = new CodeVariableDeclarationStatement(propertyType, varName);
265                         constructor.Statements.Add(decl);
266
267
268                         CodeMethodInvokeExpression call = new CodeMethodInvokeExpression(
269                                         new CodeTypeReferenceExpression(attachedTo),
270                                         "Set" + propertyName,
271                                         (CodeExpression)peek(),
272                                         new CodeVariableReferenceExpression(varName));
273
274                         push(call);
275                         push(new CodeVariableReferenceExpression(varName));
276                 }
277
278                 // pops 2 items: the name of the property, and the object to attach to
279                 public void EndDependencyProperty()
280                 {
281                         debug();
282                         pop(); // pop the variable name - we don't need it since it's already baked into the call
283                         CodeExpression call = (CodeExpression)pop();
284                         constructor.Statements.Add(call);
285                 }
286
287                 // top of stack must be an object reference
288                 public void CreateObjectText(string text)
289                 {
290                         debug();
291                         CodeVariableReferenceExpression var = (CodeVariableReferenceExpression)peek();
292                         CodeMethodInvokeExpression call = new CodeMethodInvokeExpression(
293                                         var,
294                                         "AddText",
295                                         new CodePrimitiveExpression(text));
296                         constructor.Statements.Add(call);
297                 }
298
299                 // top of stack is reference to an event
300                 public void CreateEventDelegate(string functionName, Type eventDelegateType)
301                 {
302                         debug();
303                         CodeExpression expr = new CodeObjectCreateExpression(
304                                         eventDelegateType,
305                                         new CodeMethodReferenceExpression(
306                                                         new CodeThisReferenceExpression(),
307                                                         functionName));
308                         CodeAttachEventStatement attach = new CodeAttachEventStatement(
309                                         (CodeEventReferenceExpression)peek(),
310                                         expr);
311                         constructor.Statements.Add(attach);
312
313                 }
314                 // top of stack is reference to a property
315                 public void CreatePropertyDelegate(string functionName, Type propertyType)
316                 {
317                         debug();
318                         CodeExpression expr = new CodeObjectCreateExpression(
319                                         propertyType,
320                                         new CodeMethodReferenceExpression(
321                                                         new CodeThisReferenceExpression(),
322                                                         functionName));
323                         CodeAssignStatement assignment = new CodeAssignStatement(
324                                         (CodeExpression)peek(),
325                                         expr);
326                         constructor.Statements.Add(assignment);
327                 }
328
329                 private CodeExpression fetchConverter(Type propertyType)
330                 {
331                         return new CodeMethodInvokeExpression(
332                                         new CodeMethodReferenceExpression(
333                                                         new CodeTypeReferenceExpression(typeof(System.ComponentModel.TypeDescriptor)),
334                                                         "GetConverter"),
335                                         new CodeTypeOfExpression(propertyType));
336                 }
337
338                 // top of stack is reference to a property
339                 public void CreatePropertyText(string text, Type propertyType)
340                 {
341                         debug();
342                         CreateDependencyPropertyText(text, propertyType);
343                 }
344                 // top of stack is reference to an attached property
345                 public void CreateDependencyPropertyText(string text, Type propertyType)
346                 {
347                         debug();
348                         CodeExpression expr = new CodePrimitiveExpression(text);
349                         if (propertyType != typeof(string)) {
350                                 expr = new CodeCastExpression(
351                                                 new CodeTypeReference(propertyType),
352                                                 new CodeMethodInvokeExpression(
353                                                                 fetchConverter(propertyType),
354                                                                 "ConvertFromString",
355                                                                 expr));
356                         }
357                         CodeAssignStatement assignment = new CodeAssignStatement(
358                                         (CodeExpression)peek(),
359                                         expr);
360                         
361                         constructor.Statements.Add(assignment);
362                 }
363
364                 public void CreatePropertyObject(Type type, string varName)
365                 {
366                         debug();
367                         bool isDefaultName;
368                         if (varName == null) {
369                                 isDefaultName = true;
370                                 varName = Char.ToLower(type.Name[0]) + type.Name.Substring(1);
371                                 // make sure something sensible happens when class
372                                 // names start with a lowercase letter
373                                 if (varName == type.Name)
374                                         varName = "_" + varName;
375                         } else {
376                                 isDefaultName = false;
377                         }
378
379                         if (isDefaultName) {
380                                 if (!nameClashes.ContainsKey(varName))
381                                         nameClashes[varName] = 0;
382                                 nameClashes[varName] = 1 + (int)nameClashes[varName];
383                                 varName += (int)nameClashes[varName];
384                         }
385
386
387                         if (isDefaultName) {
388                                 CodeVariableDeclarationStatement declaration = 
389                                                 new CodeVariableDeclarationStatement(type, 
390                                                                 varName,
391                                                                 new CodeObjectCreateExpression(type));
392                                 constructor.Statements.Add(declaration);
393                         } else {
394                                 CodeMemberField declaration = new CodeMemberField(type, varName);
395                                 declaration.InitExpression = new CodeObjectCreateExpression(type);
396                                 this.type.Members.Add(declaration);
397                         }
398                         CodeVariableReferenceExpression varRef = new CodeVariableReferenceExpression(varName);
399
400                         push(type);
401                         push(varRef);
402                 
403                 }
404
405                 public void EndPropertyObject(Type destType)
406                 {
407                         debug();
408                         CodeExpression varRef = (CodeExpression)pop();
409                         Type sourceType = (Type)pop();
410
411                         Debug.WriteLine("CodeWriter: " + destType + "->" + sourceType);
412
413                         
414                         CodeExpression expr;
415                         if (sourceType == destType || sourceType.IsSubclassOf(destType))
416                                 expr = varRef;
417                         else
418                                 expr = new CodeCastExpression(
419                                                 new CodeTypeReference(destType),
420                                                 new CodeMethodInvokeExpression(
421                                                                 fetchConverter(sourceType),
422                                                                 "ConvertTo",
423                                                                 varRef,
424                                                                 new CodeTypeOfExpression(destType)));
425                         CodeAssignStatement assignment = new CodeAssignStatement(
426                                         (CodeExpression)peek(),
427                                         expr);
428                         constructor.Statements.Add(assignment);
429                 }
430                 
431                 public void EndObject()
432                 {
433                         debug();
434                         pop();
435                 }
436
437                 public void EndProperty()
438                 {
439                         debug();
440                         pop();
441                 }
442                 
443                 public void EndEvent()
444                 {
445                         debug();
446                         pop();
447                 }
448
449                 public void Finish()
450                 {
451                         debug();
452                         generator.GenerateCodeFromCompileUnit(code, writer, null);
453                         writer.Close();
454                 }
455
456                 public void CreateCode(string code)
457                 {
458                         debug();
459                         type.Members.Add(new CodeSnippetTypeMember(code));
460                 }
461
462                 private void debug()
463                 {
464                         Debug.WriteLine("CodeWriter: " + new System.Diagnostics.StackTrace());
465                 }
466                 
467                 private object pop()
468                 {
469                         object v = objects[objects.Count - 1];
470                         objects.RemoveAt(objects.Count - 1);
471                         Debug.WriteLine("CodeWriter: POPPING");
472                         return v;
473                 }
474                 private void push(object v)
475                 {
476                         Debug.WriteLine("CodeWriter: PUSHING " + v);
477                         objects.Add(v);
478                 }
479                 private object peek()
480                 {
481                         return peek(0);
482                 }
483                 private object peek(int i)
484                 {
485                         return objects[objects.Count - 1 - i];
486                 }
487         }
488 }