fb5266eb1310eea6c3cd141d8e608e0c68c17545
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / Microsoft.Tools.Common / Microsoft / Activities / Presentation / Xaml / ViewStateXamlHelper.cs
1 // <copyright>
2 //   Copyright (c) Microsoft Corporation.  All rights reserved.
3 // </copyright>
4
5 namespace Microsoft.Activities.Presentation.Xaml
6 {
7     using System;
8     using System.Activities;
9     using System.Activities.Debugger;
10     using System.Activities.DynamicUpdate;
11     using System.Activities.Presentation.View;
12     using System.Activities.Presentation.ViewState;
13     using System.Collections.Generic;
14     using System.Reflection;
15     using System.Runtime;
16     using System.Xaml;
17
18     internal static class ViewStateXamlHelper
19     {
20         static readonly string ViewStateManager = WorkflowViewState.ViewStateManagerProperty.MemberName;
21         static readonly string IdRef = WorkflowViewState.IdRefProperty.MemberName;
22         static readonly MethodInfo GetViewStateManager = typeof(WorkflowViewState).GetMethod("GetViewStateManager");
23         static readonly MethodInfo SetViewStateManager = typeof(WorkflowViewState).GetMethod("SetViewStateManager");
24         static readonly MethodInfo GetIdRef = typeof(WorkflowViewState).GetMethod("GetIdRef");
25         static readonly MethodInfo SetIdRef = typeof(WorkflowViewState).GetMethod("SetIdRef");
26         static readonly List<string> SourceLocationNames = new List<string> 
27             {
28                 XamlDebuggerXmlReader.StartLineName.MemberName,
29                 XamlDebuggerXmlReader.StartColumnName.MemberName,
30                 XamlDebuggerXmlReader.EndLineName.MemberName,
31                 XamlDebuggerXmlReader.EndColumnName.MemberName
32             };
33
34         // These are used to discover that we have found a DynamicUpdateInfo.OriginalDefintion or OriginalActivityBuilder
35         // attached property member. We have "hardcoded" the *MemberName" here because DynamicUpdateInfo has the
36         // AttachableMemberIdentifier properties marked as private. But the DynamicUpdateInfo class itself is public,
37         // as are the Get and Set methods.
38         static readonly string DynamicUpdateOriginalDefinitionMemberName = "OriginalDefinition";
39         static readonly MethodInfo GetOriginalDefinition = typeof(DynamicUpdateInfo).GetMethod("GetOriginalDefinition");
40         static readonly MethodInfo SetOriginalDefinition = typeof(DynamicUpdateInfo).GetMethod("SetOriginalDefinition");
41
42         static readonly string DynamicUpdateOriginalActivityBuilderMemberName = "OriginalActivityBuilder";
43         static readonly MethodInfo GetOriginalActivityBuilder = typeof(DynamicUpdateInfo).GetMethod("GetOriginalActivityBuilder");
44         static readonly MethodInfo SetOriginalActivityBuilder = typeof(DynamicUpdateInfo).GetMethod("SetOriginalActivityBuilder");
45
46         // This method collects view state attached properties and generates a Xaml node stream 
47         // with all view state information appearing within the ViewStateManager node. 
48         // It is called when workflow definition is being serialized to string.
49         // inputReader  - Nodestream with view state information as attached properties on the activity nodes.
50         //                The reader is positioned at the begining of the workflow definition.
51         // idManager    - This component issues running sequence numbers for IdRef.
52         // Result       - Node stream positioned at the begining of the workflow definition with a  
53         //                ViewStateManager node containing all view state information.
54         // Implementation logic:
55         // 1. Scan the input nodestream Objects for attached properties that need to be converted (VirtualizedContainerService.HintSize and WorkflowViewStateService.ViewState).
56         // 2. If the Object had a IdRef value then use it otherwise generate a new value.
57         // 3. Store idRef value and corresponding viewstate related attached property nodes (from step 1) 
58         //    in the viewStateInfo dictionary.
59         // 4. Use the viewStateInfo dictionary to generate ViewStateManager node which is then inserted
60         //    into the end of output nodestream.
61         public static XamlReader ConvertAttachedPropertiesToViewState(XamlObjectReader inputReader, ViewStateIdManager idManager)
62         {
63             // Stack to track StartObject/GetObject and EndObject nodes.
64             Stack<Frame> stack = new Stack<Frame>();
65
66             XamlMember viewStateManager = new XamlMember(ViewStateManager, GetViewStateManager, SetViewStateManager, inputReader.SchemaContext);
67             XamlMember idRefMember = new XamlMember(IdRef, GetIdRef, SetIdRef, inputReader.SchemaContext);
68             
69             // Xaml member corresponding to x:Class property of the workflow definition. Used to find x:Class value in the node stream.
70             XamlMember activityBuilderName = new XamlMember(typeof(ActivityBuilder).GetProperty("Name"), inputReader.SchemaContext);
71             string activityBuilderTypeName = typeof(ActivityBuilder).Name;
72
73             // Dictionary to keep track of IdRefs and corresponding viewstate related
74             // attached property nodes.
75             Dictionary<string, XamlNodeList> viewStateInfo = new Dictionary<string, XamlNodeList>();
76
77             // Output node list
78             XamlNodeList workflowDefinition = new XamlNodeList(inputReader.SchemaContext);
79
80             using (XamlWriter workflowDefinitionWriter = workflowDefinition.Writer)
81             {
82                 bool design2010NamespaceFound = false;
83                 bool inIdRefMember = false;
84                 bool inxClassMember = false;
85                 bool skipWritingWorkflowDefinition = false;
86                 bool skipReadingWorkflowDefinition = false;
87                 string xClassName = null;
88
89                 while (skipReadingWorkflowDefinition || inputReader.Read())
90                 {
91                     skipWritingWorkflowDefinition = false;
92                     skipReadingWorkflowDefinition = false;
93                     switch (inputReader.NodeType)
94                     {
95                         case XamlNodeType.NamespaceDeclaration:
96                             if (inputReader.Namespace.Namespace.Equals(NameSpaces.Design2010, StringComparison.Ordinal))
97                             {
98                                 design2010NamespaceFound = true;
99                             }
100                             break;
101
102                         case XamlNodeType.StartObject:
103                             // Save the Xaml type and clr object on the stack frame. These are used later to generate 
104                             // IdRef values and attaching the same to the clr object.
105                             stack.Push(new Frame() { Type = inputReader.Type, InstanceObject = inputReader.Instance });
106
107                             // If the design2010 namespace was not found add the namespace node
108                             // before the start object is written out.
109                             if (!design2010NamespaceFound)
110                             {
111                                 workflowDefinitionWriter.WriteNamespace(new NamespaceDeclaration(NameSpaces.Design2010, NameSpaces.Design2010Prefix));
112                                 design2010NamespaceFound = true;
113                             }
114                             break;
115
116                         case XamlNodeType.GetObject:
117                             // Push an empty frame to balance the Pop operation when the EndObject node 
118                             // is encountered.
119                             stack.Push(new Frame() { Type = null });
120                             break;
121
122                         case XamlNodeType.StartMember:
123                             // Track when we enter IdRef member so that we can save its value.
124                             if (inputReader.Member.Equals(idRefMember))
125                             {
126                                 inIdRefMember = true;
127                             }
128                             // Track when we enter x:Class member so that we can save its value.
129                             else if (inputReader.Member.Equals(activityBuilderName))
130                             {
131                                 inxClassMember = true;
132                             }
133                             // Start of VirtualizedContainerService.HintSize or WorkflowViewStateService.ViewState property.
134                             else if (IsAttachablePropertyForConvert(inputReader)) 
135                             {
136                                 // The top of stack here corresponds to the activity on which
137                                 // the above properties are attached.
138                                 if (stack.Peek().AttachedPropertyNodes == null)
139                                 {
140                                     stack.Peek().AttachedPropertyNodes = new XamlNodeList(inputReader.SchemaContext);
141                                 }
142
143                                 // Write the attached property's xaml nodes into the stack.
144                                 XamlReader subTreeReader = inputReader.ReadSubtree();
145                                 XamlWriter attachedPropertyWriter = stack.Peek().AttachedPropertyNodes.Writer;
146                                 while (subTreeReader.Read())
147                                 {
148                                     attachedPropertyWriter.WriteNode(subTreeReader);
149                                 }
150
151                                 // The subtree reader loop put us at the begining of the next node in the input stream. 
152                                 // So skip reading/writing it out just yet.
153                                 skipReadingWorkflowDefinition = true;
154                                 skipWritingWorkflowDefinition = true;
155                             }
156                             break;
157
158                         case XamlNodeType.Value:
159                             // Read and save IdRef/x:Class member values.
160                             // Also update idManager to keep track of prefixes and ids seen.
161                             if (inIdRefMember)
162                             {
163                                 string idRef = inputReader.Value as string;
164                                 stack.Peek().IdRef = idRef;
165                                 idManager.UpdateMap(idRef); 
166                             }
167                             else if (inxClassMember)
168                             {
169                                 xClassName = inputReader.Value as string;
170                                 idManager.UpdateMap(xClassName);
171                             }
172                             break;
173
174                         case XamlNodeType.EndMember:
175                             // Exit IdRef/x:Class member state.
176                             if (inIdRefMember)
177                             {
178                                 inIdRefMember = false;
179                             }
180                             else if (inxClassMember)
181                             {
182                                 inxClassMember = false;
183                             }
184                             break;
185
186                         case XamlNodeType.EndObject:
187                             // Remove an item from the stack because we encountered the end of an object definition.
188                             Frame frameObject = stack.Pop();
189
190                             // If the object had (viewstate related) attached properties we need to save them
191                             // into the viewStateInfo dictionary.
192                             if (frameObject.AttachedPropertyNodes != null)
193                             {
194                                 frameObject.AttachedPropertyNodes.Writer.Close();
195
196                                 // If the object didn't have IdRef, generate a new one.
197                                 if (string.IsNullOrWhiteSpace(frameObject.IdRef))
198                                 {
199                                     // Use the object type name (or x:Class value) to generate a new id.
200                                     if (frameObject.Type != null)
201                                     {
202                                         string prefix = frameObject.Type.Name;
203                                         if (frameObject.Type.UnderlyingType != null)
204                                         {
205                                             prefix = frameObject.Type.UnderlyingType.Name;
206                                         }
207
208                                         if (string.CompareOrdinal(prefix, activityBuilderTypeName) == 0 && !string.IsNullOrWhiteSpace(xClassName))
209                                         {
210                                             frameObject.IdRef = idManager.GetNewId(xClassName);
211                                         }
212                                         else
213                                         {
214                                             frameObject.IdRef = idManager.GetNewId(prefix);
215                                         }
216                                     }
217                                     else //Fallback to generating a guid value.
218                                     {
219                                         frameObject.IdRef = Guid.NewGuid().ToString();
220                                     }
221
222                                     // Since we didn't see a IdRef on this object, insert the generated 
223                                     // viewstate id into the output Xaml node-stream.
224                                     workflowDefinitionWriter.WriteStartMember(idRefMember);
225                                     workflowDefinitionWriter.WriteValue(frameObject.IdRef);
226                                     workflowDefinitionWriter.WriteEndMember();
227
228                                     // Save the generated idRef on the corresponding clr object as well.
229                                     if (frameObject.InstanceObject != null)
230                                     {
231                                         WorkflowViewState.SetIdRef(frameObject.InstanceObject, frameObject.IdRef);
232                                     }
233                                 }
234
235                                 viewStateInfo[frameObject.IdRef] = frameObject.AttachedPropertyNodes;
236                             }
237
238                             // We're at the end of input nodestream and have collected data in viewStateInfo
239                             // so we need to create and insert the ViewStateManager nodes into the output nodestream.
240                             if (stack.Count == 0 && viewStateInfo.Count > 0)
241                             {
242                                 XamlNodeList viewStateManagerNodeList = CreateViewStateManagerNodeList(viewStateInfo, inputReader.SchemaContext);
243                                 XamlReader viewStateManagerNodeReader = viewStateManagerNodeList.GetReader();
244
245                                 // Insert the ViewStateManager nodes into the output node stream.
246                                 workflowDefinitionWriter.WriteStartMember(viewStateManager);
247                                 while (viewStateManagerNodeReader.Read())
248                                 {
249                                     workflowDefinitionWriter.WriteNode(viewStateManagerNodeReader);
250                                 }
251                                 workflowDefinitionWriter.WriteEndMember(); // viewStateManager
252                             }
253                             break;
254                     }
255
256                     if (!skipWritingWorkflowDefinition)
257                     {
258                         workflowDefinitionWriter.WriteNode(inputReader);
259                     }
260                 }
261             }
262
263             return workflowDefinition.GetReader();
264         }
265
266         // This method converts view state information stored within the ViewStateManager node back as
267         // attached properties on corresponding activity nodes.
268         // It is called when workflow definition is being deserialized from a string.
269         // inputReader - Nodestream that may have all view state information in the ViewStateManager node at the end of workflow definition. 
270         //               The reader is positioned at the begining of the workflow definition.
271         // idManager   - This component issues running sequence numbers for IdRef.
272         // viewStateManager - (output) ViewStateManager object instance deserialized from the workflow definition.
273         // Result      - Node stream positioned at the begining of the workflow definition with view state related information
274         //               appearing as attached properties on activities. The ViewStateManager nodes are removed from the stream.
275         // Implementation logic:
276         // 1. Scan the input nodestream for ViewStateManager node.
277         // 2. If ViewStateManager node is found, store Id and corresponding attached property nodes 
278         //    in viewStateInfo dictionary. Otherwise return early.
279         // 3. Walk activity nodes in the workflow definition and apply viewstate related attached properties (from 
280         //    viewStateInfo dictionary) to each node.
281         // 4. If multiple activities have same IdRef values then corresponding viewstate related attached properties 
282         //    (from viewStateInfo dictionary) are applied to the first of those activities. The other activities with duplicate
283         //    IdRef values do not get view state information.
284         public static XamlReader ConvertViewStateToAttachedProperties(XamlReader inputReader, ViewStateIdManager idManager, out Dictionary<string, SourceLocation> viewStateSourceLocationMap)
285         {
286             int idRefLineNumber = 0;
287             int idRefLinePosition = 0;
288             bool shouldWriteIdRefEndMember = false;
289
290             XamlReader retVal = null;
291
292             // Xaml member definition for IdRef. Used to identify existing IdRef properties in the input nodestream.
293             XamlMember idRefMember = new XamlMember(IdRef, GetIdRef, SetIdRef, inputReader.SchemaContext);
294
295             // These are used to ignore the IdRef members that are inside a DynamicUpdateInfo.OriginalDefinition/OriginalActivityBuilder attached property.
296             // We need to ignore these because if we don't, the IdRef values for the objects in the actual workflow defintion will be ignored because of the
297             // duplicate IdRef value. This causes problems with activity designers that depend on the ViewStateManager data to correctly display the workflow
298             // on the WorkflowDesigner canvas.
299             XamlMember originalDefinitionMember = new XamlMember(DynamicUpdateOriginalDefinitionMemberName, GetOriginalDefinition, SetOriginalDefinition, inputReader.SchemaContext);
300             XamlMember originalActivityBuilderMember = new XamlMember(DynamicUpdateOriginalActivityBuilderMemberName, GetOriginalActivityBuilder, SetOriginalActivityBuilder, inputReader.SchemaContext);
301
302             // insideOriginalDefintion gets set to true when we find a "StartMember" node for either of the above two attached properties.
303             // originalDefintionMemberCount gets incremented if we find any "StartMember" and insideOriginalDefinition is true.
304             // originalDefintionMemberCount gets decremented if we find any "EndMember" and insideOriginalDefintion is true.
305             // insideOriginalDefintion gets set to false when we find an "EndMember" and originalDefinitionMemberCount gets decremented to 0.
306             // If insideOriginalDefintion is true when we find an "IdRef" member, we do NOT add that IdRef to the idRefsSeen HashSet to avoid
307             // duplicates being defined by the IdRefs inside of the OriginalDefinition attached properties.
308             bool insideOriginalDefinition = false;
309             int originalDefinitionMemberCount = 0;
310
311             // Dictionary containing Ids and corresponding viewstate related
312             // attached property nodes. Populated by StripViewStateElement method.
313             Dictionary<string, XamlNodeList> viewStateInfo = null;
314             XamlReader workflowDefinition = StripViewStateElement(inputReader, out viewStateInfo, out viewStateSourceLocationMap);
315
316             // This is used to keep track of duplicate IdRefs in the workflow definition.
317             HashSet<string> idRefsSeen = new HashSet<string>();
318             
319             // If the inputReader did not have a ViewStateManager node (4.0 format)
320             // return early.
321             if (viewStateInfo == null)
322             {
323                 retVal = workflowDefinition;
324             }
325             else
326             {
327                 // Stack to track StartObject/GetObject and EndObject nodes.
328                 Stack<Frame> stack = new Stack<Frame>();
329
330                 // Output node list.
331                 XamlNodeList mergedNodeList = new XamlNodeList(workflowDefinition.SchemaContext);
332                 bool inIdRefMember = false;
333
334                 using (XamlWriter mergedNodeWriter = mergedNodeList.Writer)
335                 {
336                     IXamlLineInfo lineInfo = workflowDefinition as IXamlLineInfo;
337                     IXamlLineInfoConsumer lineInfoComsumer = mergedNodeWriter as IXamlLineInfoConsumer;
338                     bool shouldPassLineInfo = lineInfo != null && lineInfo.HasLineInfo && lineInfoComsumer != null && lineInfoComsumer.ShouldProvideLineInfo;
339
340                     while (workflowDefinition.Read())
341                     {
342                         bool skipWritingWorkflowDefinition = false;
343
344                         switch (workflowDefinition.NodeType)
345                         {
346                             case XamlNodeType.StartObject:
347                                 stack.Push(new Frame { Type = workflowDefinition.Type });
348                                 break;
349
350                             case XamlNodeType.GetObject:
351                                 stack.Push(new Frame { Type = null });
352                                 break;
353
354                             case XamlNodeType.StartMember:
355                                 // If we find a StartMember for DynamicUpdateInfo.OriginalDefinition or OriginalActivityBuilder, remember that we are
356                                 // inside one of those. We don't want to "remember" IdRef values in the idRefsSeen HashSet while inside these attached properties.
357                                 if (workflowDefinition.Member.Equals(originalDefinitionMember) || workflowDefinition.Member.Equals(originalActivityBuilderMember))
358                                 {
359                                     insideOriginalDefinition = true;
360                                 }
361                                 
362                                 if (insideOriginalDefinition)
363                                 {
364                                     originalDefinitionMemberCount++;
365                                 }
366
367                                 // Track when the reader enters IdRef. Skip writing the start 
368                                 // node to the output nodelist until we check for duplicates.
369                                 else if (workflowDefinition.Member.Equals(idRefMember))
370                                 {
371                                     inIdRefMember = true;
372                                     skipWritingWorkflowDefinition = true;
373
374                                     if (shouldPassLineInfo)
375                                     {
376                                         idRefLineNumber = lineInfo.LineNumber;
377                                         idRefLinePosition = lineInfo.LinePosition;
378                                     }
379                                 }
380                                 break;
381
382                             case XamlNodeType.Value:
383                                 if (inIdRefMember)
384                                 {
385                                     // We don't want to deal with the IdRef if we are inside a DynamicUpdateInfo.OriginalDefinition/OriginalActivityBuilder
386                                     // attached property.
387                                     if (!insideOriginalDefinition)
388                                     {
389                                         string idRef = workflowDefinition.Value as string;
390                                         if (!string.IsNullOrWhiteSpace(idRef))
391                                         {
392                                             // If IdRef value is a duplicate then do not associate it with 
393                                             // the stack frame (top of stack == activity node with IdRef member on it).
394                                             if (idRefsSeen.Contains(idRef))
395                                             {
396                                                 stack.Peek().IdRef = null;
397                                             }
398                                             // If the IdRef value is unique then associate it with the
399                                             // stack frame and also write its value into the output nodestream.
400                                             else
401                                             {
402                                                 stack.Peek().IdRef = idRef;
403                                                 idManager.UpdateMap(idRef);
404                                                 idRefsSeen.Add(idRef);
405
406                                                 if (shouldPassLineInfo)
407                                                 {
408                                                     lineInfoComsumer.SetLineInfo(idRefLineNumber, idRefLinePosition);
409                                                 }
410
411                                                 mergedNodeWriter.WriteStartMember(idRefMember);
412
413                                                 if (shouldPassLineInfo)
414                                                 {
415                                                     lineInfoComsumer.SetLineInfo(lineInfo.LineNumber, lineInfo.LinePosition);
416                                                 }
417
418                                                 mergedNodeWriter.WriteValue(idRef);
419
420                                                 shouldWriteIdRefEndMember = true;
421                                             }
422                                         }
423                                     }
424                                     // Don't need to write IdRef value into the output
425                                     // nodestream. If the value was valid, it would have been written above.
426                                     skipWritingWorkflowDefinition = true;
427                                 }
428                                 break;
429
430                             case XamlNodeType.EndMember:
431                                 // If we are inside an OriginalDefinition/OriginalActivityBuilder attached property,
432                                 // decrement the count and if it goes to zero, set insideOriginalDefintion to false
433                                 // because we just encountered the EndMember for it.
434                                 if (insideOriginalDefinition)
435                                 {
436                                     originalDefinitionMemberCount--;
437                                     if (originalDefinitionMemberCount == 0)
438                                     {
439                                         insideOriginalDefinition = false;
440                                     }
441                                 }
442
443                                 // Exit IdRef node. Skip writing the EndMember node, we would have done 
444                                 // it as part of reading the IdRef value.
445                                 if (inIdRefMember && !insideOriginalDefinition)
446                                 {
447                                     inIdRefMember = false;
448                                     skipWritingWorkflowDefinition = true;
449
450                                     if (shouldWriteIdRefEndMember)
451                                     {
452                                         shouldWriteIdRefEndMember = false;
453                                         
454                                         if (shouldPassLineInfo)
455                                         {
456                                             lineInfoComsumer.SetLineInfo(lineInfo.LineNumber, lineInfo.LinePosition);
457                                         }
458
459                                         mergedNodeWriter.WriteEndMember();
460                                     }
461                                 }
462
463                                 break;
464
465                             case XamlNodeType.EndObject:
466                                 Frame frameObject = stack.Pop();
467                                 // Before we exit the end of an object, check if it had IdRef
468                                 // associated with it. If it did, look-up viewStateInfo for viewstate
469                                 // related attached property nodes and add them to the output nodelist.
470                                 if (!string.IsNullOrWhiteSpace(frameObject.IdRef))
471                                 {
472                                     XamlNodeList viewStateNodeList;
473                                     if (viewStateInfo.TryGetValue(frameObject.IdRef, out viewStateNodeList))
474                                     {
475                                         XamlReader viewStateReader = viewStateNodeList.GetReader();
476
477                                         IXamlLineInfo viewStateLineInfo = viewStateReader as IXamlLineInfo;
478                                         bool viewStateShouldPassLineInfo = viewStateLineInfo != null && viewStateLineInfo.HasLineInfo && lineInfoComsumer != null && lineInfoComsumer.ShouldProvideLineInfo;
479
480                                         while (viewStateReader.Read())
481                                         {
482                                             if (viewStateShouldPassLineInfo)
483                                             {
484                                                 lineInfoComsumer.SetLineInfo(viewStateLineInfo.LineNumber, viewStateLineInfo.LinePosition);
485                                             }
486
487                                             mergedNodeWriter.WriteNode(viewStateReader);
488                                         }
489                                     }
490                                 }
491                                 break;
492                         }
493                         if (!skipWritingWorkflowDefinition)
494                         {
495                             if (shouldPassLineInfo)
496                             {
497                                 lineInfoComsumer.SetLineInfo(lineInfo.LineNumber, lineInfo.LinePosition);
498                             }
499
500                             mergedNodeWriter.WriteNode(workflowDefinition);
501                         }
502                     }
503                 }
504
505                 retVal = mergedNodeList.GetReader();
506             }
507
508             return retVal;
509         }
510
511         // This method removes IdRef nodes from the nodestream. This method would be called
512         // when a 4.5 workflow definition is retargeted to 4.0.
513         public static XamlReader RemoveIdRefs(XamlObjectReader inputReader)
514         {
515             XamlMember idRefMember = new XamlMember(IdRef, GetIdRef, SetIdRef, inputReader.SchemaContext);
516
517             XamlNodeList outputNodeList = new XamlNodeList(inputReader.SchemaContext);
518             using (XamlWriter outputWriter = outputNodeList.Writer)
519             {
520                 while (inputReader.Read())
521                 {
522                     if (inputReader.NodeType == XamlNodeType.StartMember && inputReader.Member.Equals(idRefMember))
523                     {
524                         // Exhaust the idRefMember sub-tree.
525                         XamlReader idRefReader = inputReader.ReadSubtree();
526                         while (idRefReader.Read());
527                     }
528                     outputWriter.WriteNode(inputReader);
529                 }
530             }
531             return outputNodeList.GetReader();
532         }
533
534         // This is a helper method to output the nodestream sequence for debugging/diagnostic purposes.
535         public static void NodeLoopTest(XamlReader xamlReader)
536         {
537 #if DEBUG
538             string tabs = "";
539             int depth = 1;
540             while (xamlReader.Read())
541             {
542                 switch (xamlReader.NodeType)
543                 {
544                     case XamlNodeType.NamespaceDeclaration:
545                         System.Diagnostics.Debug.WriteLine(tabs + "Namespace declaration: {0}:{1}", xamlReader.Namespace.Prefix, xamlReader.Namespace.Namespace);
546                         break;
547                     case XamlNodeType.StartObject:
548                         tabs = new String(' ', depth++);
549                         System.Diagnostics.Debug.WriteLine(tabs + "Start object: {0}", xamlReader.Type.Name);
550                         break;
551                     case XamlNodeType.GetObject:
552                         tabs = new String(' ', depth++);
553                         System.Diagnostics.Debug.WriteLine(tabs + "Get object");
554                         break;
555                     case XamlNodeType.StartMember:
556                         tabs = new String(' ', depth++);
557                         System.Diagnostics.Debug.WriteLine(tabs + "Start member: {0}, Attachable: {1}", xamlReader.Member.Name, xamlReader.Member.IsAttachable);
558                         break;
559                     case XamlNodeType.Value:
560                         tabs = new String(' ', depth++);
561                         System.Diagnostics.Debug.WriteLine(tabs + "Value: {0}", xamlReader.Value);
562                         --depth;
563                         break;
564                     case XamlNodeType.EndMember:
565                         tabs = new String(' ', --depth);
566                         System.Diagnostics.Debug.WriteLine(tabs + "End member");
567                         break;
568                     case XamlNodeType.EndObject:
569                         tabs = new String(' ', --depth);
570                         System.Diagnostics.Debug.WriteLine(tabs + "End object");
571                         break;
572                 }
573             }
574 #endif
575         }
576
577         // Given the viewStateInfo dictionary, this method returns a xaml node list matching a ViewStateManager
578         // object.
579         static XamlNodeList CreateViewStateManagerNodeList(Dictionary<string, XamlNodeList> viewStateInfo, XamlSchemaContext schemaContext)
580         {
581             XamlNodeList viewStateManagerNodeList = new XamlNodeList(schemaContext);
582
583             XamlMember viewStateDataMember = new XamlMember(typeof(ViewStateManager).GetProperty("ViewStateData"), schemaContext);
584             XamlType viewStateManagerType = new XamlType(typeof(ViewStateManager), schemaContext);
585             XamlType viewStateDataType = new XamlType(typeof(ViewStateData), schemaContext);
586             XamlMember idMember = new XamlMember(typeof(ViewStateData).GetProperty("Id"), schemaContext);
587
588             using (XamlWriter viewStateManagerNodeWriter = viewStateManagerNodeList.Writer)
589             {
590                 viewStateManagerNodeWriter.WriteStartObject(viewStateManagerType);
591                 viewStateManagerNodeWriter.WriteStartMember(viewStateDataMember);
592                 viewStateManagerNodeWriter.WriteGetObject();
593                 viewStateManagerNodeWriter.WriteStartMember(XamlLanguage.Items);
594
595                 foreach (KeyValuePair<string, XamlNodeList> entry in viewStateInfo)
596                 {
597                     viewStateManagerNodeWriter.WriteStartObject(viewStateDataType);
598
599                     viewStateManagerNodeWriter.WriteStartMember(idMember);
600                     viewStateManagerNodeWriter.WriteValue(entry.Key);
601                     viewStateManagerNodeWriter.WriteEndMember(); // idMember
602
603                     XamlReader viewStateValueReader = entry.Value.GetReader();
604                     while (viewStateValueReader.Read())
605                     {
606                         viewStateManagerNodeWriter.WriteNode(viewStateValueReader);
607                     }
608
609                     viewStateManagerNodeWriter.WriteEndObject(); // viewStateDataType
610                 }
611
612                 viewStateManagerNodeWriter.WriteEndMember(); // XamlLanguage.Items
613                 viewStateManagerNodeWriter.WriteEndObject(); // GetObject
614                 viewStateManagerNodeWriter.WriteEndMember(); // viewStateDataMember
615                 viewStateManagerNodeWriter.WriteEndObject(); // viewStateManagerType
616                 viewStateManagerNodeWriter.Close();
617             }
618
619             return viewStateManagerNodeList;
620         }
621
622         // Checks if the Xaml reader is on one of WorkflowViewStateService.ViewState or
623         // VirtualizedContainerService.HintSize members in the nodestream. These
624         // members need to be moved into the ViewStateManager node during the conversion.
625         static bool IsAttachablePropertyForConvert(XamlReader reader)
626         {
627             if (reader.NodeType == XamlNodeType.StartMember)
628             {
629                 XamlMember member = reader.Member;
630                 if (member.IsAttachable)
631                 {
632                     if (member.DeclaringType.UnderlyingType == typeof(WorkflowViewStateService) && member.Name.Equals("ViewState", StringComparison.Ordinal))
633                     {
634                         return true;
635                     }
636                     else if (member.DeclaringType.UnderlyingType == typeof(VirtualizedContainerService) && member.Name.Equals("HintSize", StringComparison.Ordinal))
637                     {
638                         return true;
639                     }
640                 }
641             }
642             return false;
643         }
644
645         // This method reads ViewStateManager nodes from the xaml nodestream and outputs that in the
646         // viewStateInfo dictionary. The input reader is positioned at the begining of the workflow definition.
647         // The method returns a reader positioned at the begining of the workflow definition with the ViewStateManager 
648         // nodes removed.
649         static XamlReader StripViewStateElement(XamlReader inputReader, out Dictionary<string, XamlNodeList> viewStateInfo, out Dictionary<string, SourceLocation> viewStateSourceLocationMap)
650         {
651             viewStateSourceLocationMap = null;
652             XamlNodeList strippedNodeList = new XamlNodeList(inputReader.SchemaContext);
653             XamlMember viewStateManager = new XamlMember(ViewStateManager, GetViewStateManager, SetViewStateManager, inputReader.SchemaContext);
654
655             using (XamlWriter strippedWriter = strippedNodeList.Writer)
656             {
657                 IXamlLineInfo lineInfo = inputReader as IXamlLineInfo;
658                 IXamlLineInfoConsumer lineInfoComsumer = strippedWriter as IXamlLineInfoConsumer;
659                 bool shouldPassLineInfo = lineInfo != null && lineInfo.HasLineInfo && lineInfoComsumer != null && lineInfoComsumer.ShouldProvideLineInfo;
660
661                 viewStateInfo = null;
662                 while (inputReader.Read())
663                 {
664                     if (inputReader.NodeType == XamlNodeType.StartMember && inputReader.Member.Equals(viewStateManager))
665                     {
666                         ReadViewStateInfo(inputReader.ReadSubtree(), out viewStateInfo, out viewStateSourceLocationMap);
667                         
668                     }
669
670                     if (shouldPassLineInfo)
671                     {
672                         lineInfoComsumer.SetLineInfo(lineInfo.LineNumber, lineInfo.LinePosition);
673                     }
674
675                     strippedWriter.WriteNode(inputReader);
676                 }
677             }
678
679             return strippedNodeList.GetReader();
680         }
681
682         // This method reads ViewStateManager nodes from the xaml nodestream and outputs that in the 
683         // viewStateInfo dictionary. The input reader is positioned on the ViewStateManagerNode in the nodestream.
684         static void ReadViewStateInfo(XamlReader inputReader, out Dictionary<string, XamlNodeList> viewStateInfo, out Dictionary<string, SourceLocation> viewStateSourceLocationMap)
685         {
686             XamlType viewStateType = new XamlType(typeof(ViewStateData), inputReader.SchemaContext);
687
688             viewStateInfo = new Dictionary<string, XamlNodeList>();
689             viewStateSourceLocationMap = new Dictionary<string, SourceLocation>();
690             bool skipReading = false;
691             while (skipReading || inputReader.Read())
692             {
693                 skipReading = false;
694                 if (inputReader.NodeType == XamlNodeType.StartObject && inputReader.Type.Equals(viewStateType))
695                 {
696                     string id;
697                     XamlNodeList viewStateNodeList;
698                     SourceLocation viewStateSourceLocation = null;
699                     ReadViewState(viewStateType, inputReader.ReadSubtree(), out id, out viewStateNodeList, out viewStateSourceLocation);
700                     if (id != null)
701                     {
702                         viewStateInfo[id] = viewStateNodeList;
703                         viewStateSourceLocationMap[id] = viewStateSourceLocation;
704                     }
705
706                     //inputReader will be positioned on the next node so no need to advance it.
707                     skipReading = true;
708                 }
709             }
710         }
711
712         // This method reads a ViewStateData node from the xaml nodestream. It outputs the Id property into viewStateId
713         // and the attached viewstate related properties in viewStateNodes. The input reader is positioned on a 
714         // ViewStateData node within ViewStateManager.
715         static void ReadViewState(XamlType viewStateType, XamlReader xamlReader, out string viewStateId, out XamlNodeList viewStateNodes, out SourceLocation sourceLocation)
716         {
717             int globalMemberLevel = 0;
718             bool skippingUnexpectedAttachedProperty = false;
719             int skippingUnexpectedAttachedPropertyLevel = 0;
720             viewStateId = null;
721             viewStateNodes = new XamlNodeList(viewStateType.SchemaContext);
722             sourceLocation = null;
723
724             Stack<Frame> objectNodes = new Stack<Frame>();
725             XamlMember idMember = new XamlMember(typeof(ViewStateData).GetProperty("Id"), xamlReader.SchemaContext);
726             int[] viewStateDataSourceLocation = new int[4];
727             int sourceLocationIndex = -1;
728
729             IXamlLineInfo lineInfo = xamlReader as IXamlLineInfo;
730             IXamlLineInfoConsumer lineInfoComsumer = viewStateNodes.Writer as IXamlLineInfoConsumer;
731             bool shouldPassLineInfo = lineInfo != null && lineInfo.HasLineInfo && lineInfoComsumer != null && lineInfoComsumer.ShouldProvideLineInfo;
732
733             while (xamlReader.Read())
734             {
735                 bool skipWritingToNodeList = false;
736                 switch (xamlReader.NodeType)
737                 {
738                     case XamlNodeType.StartObject:
739                         if (xamlReader.Type.Equals(viewStateType))
740                         {
741                             skipWritingToNodeList = true;
742                         }
743                         objectNodes.Push(new Frame { Type = xamlReader.Type });
744                         break;
745
746                     case XamlNodeType.GetObject:
747                         objectNodes.Push(new Frame { Type = null });
748                         break;
749
750                     case XamlNodeType.StartMember:
751                         globalMemberLevel++;
752                         if (xamlReader.Member.Equals(idMember))
753                         {
754                             XamlReader idNode = xamlReader.ReadSubtree();
755                             while (idNode.Read())
756                             {
757                                 if (idNode.NodeType == XamlNodeType.Value)
758                                 {
759                                     viewStateId = idNode.Value as string;
760                                 }
761                             }
762                         } 
763                         // The xamlReader.ReadSubtree and subsequent while loop to get the Id member
764                         // has moved the xamlReader forward to the next member. We need to check to see
765                         // if it is an Attached Property that we care about. If it isn't then we need to
766                         // skip it and not put it in the resulting XamlNodeList.
767                         if (globalMemberLevel == 1 && !IsAttachablePropertyForConvert(xamlReader)) 
768                         {
769                             skippingUnexpectedAttachedProperty = true;
770                         }
771                         if (skippingUnexpectedAttachedProperty)
772                         {
773                             skippingUnexpectedAttachedPropertyLevel++;
774                         }
775
776                         sourceLocationIndex = GetViewStateDataSourceLocationIndexFromCurrentReader(xamlReader);
777                         break;
778
779                     case XamlNodeType.EndMember:
780                         globalMemberLevel--;
781                         if (skippingUnexpectedAttachedProperty)
782                         {
783                             skippingUnexpectedAttachedPropertyLevel--;
784                         }
785                         break;
786                     case XamlNodeType.Value:
787                         if (xamlReader.Value is int
788                             && sourceLocationIndex >= 0 
789                             && sourceLocationIndex < viewStateDataSourceLocation.Length)
790                         {
791                             viewStateDataSourceLocation[sourceLocationIndex] = (int)xamlReader.Value;
792                         }
793
794                         break;
795                     case XamlNodeType.EndObject:
796                         Frame objectNode = objectNodes.Pop();
797                         if (objectNode.Type != null && objectNode.Type.Equals(viewStateType))
798                         {
799                             skipWritingToNodeList = true;
800                             // The ViewStateData's source location should be valid, because
801                             // before each EndObject, its SourceLocation is injected.
802                             // If not, an exception will be thrown from constructor 
803                             // of SourceLocation.
804                             sourceLocation = new SourceLocation(null,
805                                 viewStateDataSourceLocation[0],
806                                 viewStateDataSourceLocation[1],
807                                 viewStateDataSourceLocation[2],
808                                 viewStateDataSourceLocation[3]
809                                 );
810                         }
811
812                         Array.Clear(viewStateDataSourceLocation, 0, viewStateDataSourceLocation.Length);
813                         break;
814                 };
815
816                 if (!skipWritingToNodeList && !skippingUnexpectedAttachedProperty)
817                 {
818                     if (shouldPassLineInfo)
819                     {
820                         lineInfoComsumer.SetLineInfo(lineInfo.LineNumber, lineInfo.LinePosition);
821                     }
822
823                     viewStateNodes.Writer.WriteNode(xamlReader);
824                 }
825
826                 if (skippingUnexpectedAttachedPropertyLevel == 0)
827                 {
828                     skippingUnexpectedAttachedProperty = false;
829                 }
830             }
831             viewStateNodes.Writer.Close();
832         }
833
834         private static int GetViewStateDataSourceLocationIndexFromCurrentReader(XamlReader xamlReader)
835         {
836             if (xamlReader.NodeType != XamlNodeType.StartMember
837                 || xamlReader.Member == null
838                 || xamlReader.Member.UnderlyingMember == null
839                 || xamlReader.Member.UnderlyingMember.DeclaringType != typeof(XamlDebuggerXmlReader))
840             {
841                 return -1;
842             }
843
844             // if UnderlineType is XamlDebuggerXmlReader, see if it 
845             // is one of {StartLine, StartColumn, EndLine, EndColumn}
846             return SourceLocationNames.IndexOf(xamlReader.Member.Name);
847         }
848
849         // This class is used for tracking Xaml nodestream data.
850         class Frame
851         {
852             // IdRef value if any associated with the node.
853             public string IdRef { get; set; }
854
855             // XamlType of the node. Helps generating IdRef values.
856             public XamlType Type { get; set; }
857
858             // XamlNodes corresponding to viewstate related attached property nodes.
859             public XamlNodeList AttachedPropertyNodes { get; set; }
860
861             // Underlying CLR object. Used to attach generated IdRef values 
862             // when serializing workflow definition.
863             public object InstanceObject { get; set; }
864         }
865     }
866 }