Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / cdf / src / NetFx40 / System.Activities / System / Activities / Debugger / SourceLocationProvider.cs / 1638171 / SourceLocationProvider.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------
namespace System.Activities.Debugger
{
using System;
using System.Activities.Hosting;
using System.Activities.XamlIntegration;
using System.Diagnostics;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime;
using System.Reflection;
using System.Xaml;
using System.Xml;
using System.IO;
using System.Activities.Validation;
using System.Collections.ObjectModel;
// Provide SourceLocation information for activities in given root activity.
// This is integration point with Workflow project system (TBD).
// The current plan is to get SourceLocation from (in this order):
// 1. pdb (when available)
// 2a. parse xaml files available in the same project (or metadata store) or
// 2b. ask user to point to the correct xaml source.
// 3. Publish (serialize to tmp file) and deserialize it to collect SourceLocation (for loose xaml).
// Current code cover only step 3.
[DebuggerNonUserCode]
public static class SourceLocationProvider
{
[Fx.Tag.Throws(typeof(Exception), "Calls Serialize/Deserialize to temporary file")]
static internal Dictionary GetSourceLocations(Activity rootActivity, out string sourcePath, out bool isTemporaryFile)
{
sourcePath = XamlDebuggerXmlReader.GetFileName(rootActivity) as string;
isTemporaryFile = false;
Dictionary mapping;
Assembly localAssembly;
// This may not be the local assembly since it may not be the real root for x:Class
localAssembly = rootActivity.GetType().Assembly;
if (rootActivity.Parent != null)
{
localAssembly = rootActivity.Parent.GetType().Assembly;
}
if (rootActivity.Children != null && rootActivity.Children.Count > 0)
{ // In case of actual root is wrapped either in x:Class activity or CorrelationScope
Activity body = rootActivity.Children[0];
string bodySourcePath = XamlDebuggerXmlReader.GetFileName(body) as string;
if (!string.IsNullOrEmpty(bodySourcePath))
{
rootActivity = body;
sourcePath = bodySourcePath;
}
}
if (!string.IsNullOrEmpty(sourcePath))
{
SourceLocation tempSourceLocation;
Activity tempRootActivity;
if (TryGetSourceLocation(rootActivity, sourcePath, out tempSourceLocation)) // already has source location.
{
tempRootActivity = rootActivity;
}
else
{
// Need to store the file in memory temporary so don't have to re-read the file twice
// for XamlDebugXmlReader's BracketLocator.
FileInfo fi = new FileInfo(sourcePath);
byte[] buffer = new byte[fi.Length];
using (FileStream fs = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
{
fs.Read(buffer, 0, buffer.Length);
}
// Use two separate MemoryStreams so they're are not interfere to each other.
object deserializedObject = Deserialize(buffer, localAssembly);
IDebuggableWorkflowTree debuggableWorkflowTree = deserializedObject as IDebuggableWorkflowTree;
if (debuggableWorkflowTree != null)
{ // Declarative Service and x:Class case
tempRootActivity = debuggableWorkflowTree.GetWorkflowRoot();
}
else
{ // Loose XAML case.
tempRootActivity = deserializedObject as Activity;
}
Fx.Assert(tempRootActivity != null, "Unexpected workflow xaml file");
}
mapping = new Dictionary();
if (tempRootActivity != null)
{
CollectMapping(rootActivity, tempRootActivity, mapping, sourcePath);
}
}
else
{ // Publish the source file to temporary file.
string tempFileName = Path.GetTempFileName();
sourcePath = Path.ChangeExtension(tempFileName, ".xaml");
File.Move(tempFileName, sourcePath);
isTemporaryFile = true;
mapping = PublishAndCollectMapping(rootActivity, sourcePath);
}
return mapping;
}
static void Serialize(Stream writeStream, Activity activity)
{
using (XmlWriter writer = XmlWriter.Create(writeStream,
new XmlWriterSettings
{
Indent = true,
IndentChars = " "
}))
{
XamlServices.Save(writer, activity);
writer.Flush();
}
}
static object Deserialize(byte[] buffer, Assembly localAssembly)
{
using (XmlReader xmlReader = XmlReader.Create(new MemoryStream(buffer)))
{
using (XamlXmlReader xamlXmlReader = new XamlXmlReader(xmlReader, new XamlXmlReaderSettings { LocalAssembly = localAssembly, ProvideLineInfo = true }))
{
using (XamlDebuggerXmlReader xamlDebuggerReader = new XamlDebuggerXmlReader(xamlXmlReader, new StreamReader(new MemoryStream(buffer))))
{
using (XamlReader activityBuilderReader = ActivityXamlServices.CreateBuilderReader(xamlDebuggerReader))
{
return XamlServices.Load(activityBuilderReader);
}
}
}
}
}
// Publish activity to a file, return the SourceLocation mapping of activities & expressions.
static Dictionary PublishAndCollectMapping(Activity activity, string path)
{
Dictionary mapping = new Dictionary();
using (MemoryStream stream = new MemoryStream())
{
Serialize(stream, activity);
stream.Position = 0;
Activity newActivity = Deserialize(stream.GetBuffer(), activity.GetType().Assembly) as Activity;
Fx.Assert(newActivity != null, "Cannot do roundtrip serialization");
using (StreamWriter writer = new StreamWriter(path))
{
stream.Position = 0;
using (StreamReader reader = new StreamReader(stream))
{
writer.Write(reader.ReadToEnd());
}
}
CollectMapping(activity, newActivity, mapping, path);
}
return mapping;
}
// Collect mapping for activity1 and its descendants to their corresponding source location.
// activity2 is the shadow of activity1 but with SourceLocation information.
public static void CollectMapping(Activity rootActivity1, Activity rootActivity2, Dictionary mapping, string path)
{
// For x:Class, the rootActivity here may not be the real root, but it's the first child of the x:Class activity.
Activity realRoot1 = (rootActivity1.RootActivity != null) ? rootActivity1.RootActivity : rootActivity1;
if (!realRoot1.IsRuntimeReady)
{
IList validationErrors = null;
ActivityUtilities.CacheRootMetadata(realRoot1, new ActivityLocationReferenceEnvironment(), ProcessActivityTreeOptions.ValidationOptions, null, ref validationErrors);
}
// Similarly for rootActivity2.
Activity realRoot2 = (rootActivity2.RootActivity != null) ? rootActivity2.RootActivity : rootActivity2;
if (!realRoot2.IsRuntimeReady)
{
IList validationErrors = null;
ActivityUtilities.CacheRootMetadata(realRoot2, new ActivityLocationReferenceEnvironment(), ProcessActivityTreeOptions.ValidationOptions, null, ref validationErrors);
}
Queue> pairsRemaining = new Queue>();
pairsRemaining.Enqueue(new KeyValuePair(rootActivity1, rootActivity2));
KeyValuePair currentPair;
HashSet visited = new HashSet();
while (pairsRemaining.Count > 0)
{
currentPair = pairsRemaining.Dequeue();
Activity activity1 = currentPair.Key;
Activity activity2 = currentPair.Value;
visited.Add(activity1);
SourceLocation sourceLocation;
if (TryGetSourceLocation(activity2, path, out sourceLocation))
{
mapping.Add(activity1, sourceLocation);
}
else if (!((activity2 is IExpressionContainer) || (activity2 is IValueSerializableExpression))) // Expression is known not to have source location.
{
//Some activities may not have corresponding Xaml node, e.g. ActivityFaultedOutput.
Debugger.Log(2, "Workflow", "WorkflowDebugger: Does not have corresponding Xaml node for: " + activity2.DisplayName + "\n");
}
// This to avoid comparing any value expression with DesignTimeValueExpression (in designer case).
if (!((activity1 is IExpressionContainer) || (activity2 is IExpressionContainer) ||
(activity1 is IValueSerializableExpression) || (activity2 is IValueSerializableExpression)))
{
IEnumerator enumerator1 = WorkflowInspectionServices.GetActivities(activity1).GetEnumerator();
IEnumerator enumerator2 = WorkflowInspectionServices.GetActivities(activity2).GetEnumerator();
bool hasNextItem1 = enumerator1.MoveNext();
bool hasNextItem2 = enumerator2.MoveNext();
while (hasNextItem1 && hasNextItem2)
{
if (!visited.Contains(enumerator1.Current)) // avoid adding the same activity (e.g. some default implementation).
{
if (enumerator1.Current.GetType() != enumerator2.Current.GetType())
{
// Give debugger log instead of just asserting; to help user find out mismatch problem.
Debugger.Log(2, "Workflow",
"Unmatched type: " + enumerator1.Current.GetType().FullName +
" vs " + enumerator2.Current.GetType().FullName + "\n");
}
pairsRemaining.Enqueue(new KeyValuePair(enumerator1.Current, enumerator2.Current));
}
hasNextItem1 = enumerator1.MoveNext();
hasNextItem2 = enumerator2.MoveNext();
}
// If enumerators do not finish at the same time, then they have unmatched number of activities.
// Give debugger log instead of just asserting; to help user find out mismatch problem.
if (hasNextItem1 || hasNextItem2)
{
Debugger.Log(2, "Workflow", "Unmatched number of children\n");
}
}
}
}
// Get SourceLocation for object deserialized with XamlDebuggerXmlReader in deserializer stack.
static bool TryGetSourceLocation(object obj, string path, out SourceLocation sourceLocation)
{
sourceLocation = null;
int startLine, startColumn, endLine, endColumn;
if (AttachablePropertyServices.TryGetProperty(obj, XamlDebuggerXmlReader.StartLineName, out startLine) &&
AttachablePropertyServices.TryGetProperty(obj, XamlDebuggerXmlReader.StartColumnName, out startColumn) &&
AttachablePropertyServices.TryGetProperty(obj, XamlDebuggerXmlReader.EndLineName, out endLine) &&
AttachablePropertyServices.TryGetProperty(obj, XamlDebuggerXmlReader.EndColumnName, out endColumn) &&
SourceLocation.IsValidRange(startLine, startColumn, endLine, endColumn))
{
sourceLocation = new SourceLocation(path, startLine, startColumn, endLine, endColumn);
return true;
}
return false;
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.