using System;
using System.Data;
using System.Xml;
using System.Xml.Xsl;
using System.Collections.Specialized;
using System.Threading;
using EnvDTE;
using Extensibility;
using Microsoft.Office.Core;
using System.Diagnostics;
using System.Collections;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using AnticipatingMinds.DevAdvantageVSAddIn;
using AnticipatingMinds.Genesis.CodeDOM;
using AnticipatingMinds.Genesis.CodeParser;
using AnticipatingMinds.Genesis.AspNetDom;
using AnticipatingMinds.PlatformServices.ErrorReporting;
using AnticipatingMinds.PlatformServices.Globalization;
using AnticipatingMinds.CodeInsight;
using AnticipatingMinds.PlatformServices.Licensing;
namespace AnticipatingMinds.devMetricsAddIn{
/// <summary>
/// Summary description for devMetricsAddIn.
/// </summary>
public class DevMetricsAddIn
{
class BuildAssemblyMetadataThreadData
{
public CodeAssembly codeAssembly;
public ManualResetEvent userRequestedCancelEvent;
public ManualResetEvent assemblyLinkingDoneEvent;
public DevMetricsAddIn devMetrics;
public static object outputLock = new object();
}
private _DTE applicationObject;
private AddIn addInInstance;
private bool isAnalyzing = false;
private VSProjectModel vsProjectModel;
private static AssemblyResourceManager assemblyResourceManager = AssemblyResourceManager.GetInstance("AnticipatingMinds.devMetricsAddIn.Messages");
public static readonly string LicensingProductId = "devMetrics Professional Edition";
public bool IsProfessionalEdition = false;
public bool ShowTrialMenu = false;
public DevMetricsAddIn(_DTE applicationObject,AddIn addInInstance)
{
this.applicationObject = applicationObject;
this.addInInstance = addInInstance;
vsProjectModel = new VSProjectModel(applicationObject);
AnticipatingMindsLicenseStore licenseStore = new AnticipatingMindsLicenseStore();
LicensingState licensingState = GetLicensingState();
if((licensingState & LicensingState.LicenseIsValid) != 0)
IsProfessionalEdition = true;
if(!IsProfessionalEdition && (licensingState & LicensingState.LicenseHasExpired)==0)
ShowTrialMenu = true;
}
public static LicensingState GetLicensingState()
{
AnticipatingMindsLicenseStore licenseStore = new AnticipatingMindsLicenseStore();
return licenseStore.GetProductLicensingState(DevMetricsAddIn.LicensingProductId,Assembly.GetExecutingAssembly().GetName().Version);
}
public void BuildMenus()
{
// --- Overview of menus ---
// Add Menu off of VS.NET Tools
// - Then add Evaluate
// - Then add Help
// - Then add About devMetrics
object []contextGUIDS = new object[] { };
Commands commands = applicationObject.Commands;
_CommandBars commandBars = applicationObject.CommandBars;
// Create menu on tools
CommandBar toolsMenu = (CommandBar)commandBars["Tools"];
CommandBar devMetricsMenu = (CommandBar) commands.AddCommandBar( "devMetrics",
EnvDTE.vsCommandBarType.vsCommandBarTypeMenu, toolsMenu, 1);
// Create Evaluate command
Command command = commands.AddNamedCommand(addInInstance, "Analyze", "Analyze",
"Analyze code using devMetrics", true, 733, ref contextGUIDS,
(int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled);
CommandBarControl commandBarControl = command.AddControl(devMetricsMenu, 1);
// Create Help command
command = commands.AddNamedCommand(addInInstance, "Help", "Help",
"Help on using devMetrics", true, 983, ref contextGUIDS,
(int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled);
commandBarControl = command.AddControl(devMetricsMenu, 2);
// Create About devMetrics command
command = commands.AddNamedCommand(addInInstance, "AboutDevMetrics", "About devMetrics",
"About devMetrics", true, 0, ref contextGUIDS,
(int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled);
commandBarControl = command.AddControl(devMetricsMenu, 3);
// Create About devMetrics command
command = commands.AddNamedCommand(addInInstance, "RequestTrialDevMetrics", "Request trial license...",
"About devMetrics", true, 0, ref contextGUIDS,
(int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled);
commandBarControl = command.AddControl(devMetricsMenu, 4);
commandBarControl.BeginGroup=true;
}
public bool OnCommand( string commandName )
{
if ( commandName == "Analyze" )
{
isAnalyzing = true;
ThreadPool.QueueUserWorkItem(new WaitCallback(Evaluate));
return true;
}
else if ( commandName == "Help" )
{
ShowHelp();
return true;
}
else if ( commandName == "AboutDevMetrics" )
{
// show about box
AboutForm about = new AboutForm();
about.ShowDialog();
return true;
}
else if ( commandName == "RequestTrialDevMetrics" )
{
string productCode = "devMetrics_Professional_Edition_" + Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString();
System.Diagnostics.Process.Start(@"http://www.anticipatingminds.com/Content/Support/GetProductTrialLicense.aspx?product_code="+productCode,"");
return true;
}
return false;
}
public EnvDTE.vsCommandStatus CommandQueryStatus( string commandName )
{
if ( commandName == "Analyze" )
{
if(isAnalyzing || applicationObject.Solution == null || applicationObject.Solution.Projects == null || applicationObject.Solution.Projects.Count == 0)
return (EnvDTE.vsCommandStatus)((int)vsCommandStatus.vsCommandStatusSupported);
foreach(Project project in vsProjectModel.Projects)
{
if(project.Kind == VSLangProj.PrjKind.prjKindCSharpProject)
return (EnvDTE.vsCommandStatus)(vsCommandStatus.vsCommandStatusSupported + (int) vsCommandStatus.vsCommandStatusEnabled);
}
return (EnvDTE.vsCommandStatus)((int)vsCommandStatus.vsCommandStatusSupported);
}
else if ( commandName == "Help" )
{
return (EnvDTE.vsCommandStatus)(vsCommandStatus.vsCommandStatusSupported + (int) vsCommandStatus.vsCommandStatusEnabled);
}
else if ( commandName == "AboutDevMetrics" )
{
return (EnvDTE.vsCommandStatus)(vsCommandStatus.vsCommandStatusSupported + (int) vsCommandStatus.vsCommandStatusEnabled);
}
else if ( commandName == "RequestTrialDevMetrics" )
{
if(ShowTrialMenu)
return (EnvDTE.vsCommandStatus)(vsCommandStatus.vsCommandStatusSupported + (int) vsCommandStatus.vsCommandStatusEnabled);
else
return (EnvDTE.vsCommandStatus)(vsCommandStatus.vsCommandStatusSupported + (int) vsCommandStatus.vsCommandStatusInvisible);
}
return (EnvDTE.vsCommandStatus)((int)vsCommandStatus.vsCommandStatusSupported);
}
public void ShowHelp()
{
try
{
Type helpType = Type.GetTypeFromProgID("DExplore.AppObj");
object helpObject = Activator.CreateInstance(helpType);
helpType.InvokeMember("SetCollection",BindingFlags.InvokeMethod,null,helpObject,new string[] {"ms-help://AnticipatingMinds.devMetrics",""});
helpType.InvokeMember("Contents",BindingFlags.InvokeMethod,null,helpObject,null);
helpType.InvokeMember("DisplayTopicFromURL",BindingFlags.InvokeMethod,null,helpObject,new string[] {@"ms-help://AnticipatingMinds.devMetrics/AnticipatingMinds.devMetrics/Content/Welcome.htm"});
helpType.InvokeMember("SyncContents",BindingFlags.InvokeMethod,null,helpObject,new string[] {@"ms-help://AnticipatingMinds.devMetrics/AnticipatingMinds.devMetrics/Content/Welcome.htm"});
}
catch(Exception)
{
try
{
System.Diagnostics.Process.Start(@"dexplore.exe"," /helpcol ms-help://AnticipatingMinds.devMetrics/AnticipatingMinds.devMetrics/Content/Welcome.htm");
}
catch(Exception e)
{
ExceptionsManager.ReportUnhandledException( e, "devMetrics" );
}
}
}
public void Evaluate(object state)
{
try
{
try
{
CodeSolution codeSolution = BuildCodeModel();
GetOutputWindow().OutputString("Compiling solution measurements...");
GetOutputWindow().OutputString("\n");
applicationObject.StatusBar.Progress(true,"Compiling solution measurements...",90,100);
//Get licensed metrics
CodeMetric[] metrics = null;
if(IsProfessionalEdition)
{
GetOutputWindow().OutputString("devMetrics Professional Edition - All metrics are evaluated.");
GetOutputWindow().OutputString("\n");
foreach(CodeMetric metric in CodeInsight.CodeMeasurements.Metrics)
{
GetOutputWindow().OutputString(String.Format("Metric {0} will be avaluated.",metric.Name));
GetOutputWindow().OutputString("\n");
}
metrics = CodeInsight.CodeMeasurements.Metrics;
}
else
{
GetOutputWindow().OutputString("devMetrics Community Edition - Only Anticipating Minds metrics are evaluated.");
ArrayList metricsList= new ArrayList();
Byte[] anticipatingMindsPublickKey = Assembly.GetExecutingAssembly().GetName().GetPublicKey();
foreach(CodeMetric metric in CodeInsight.CodeMeasurements.Metrics)
{
byte[] publicKey = metric.GetType().Assembly.GetName().GetPublicKey();
if(publicKey == null || publicKey.Length == 0 || publicKey.Length != anticipatingMindsPublickKey.Length)
{
GetOutputWindow().OutputString(String.Format("Metric {0} will not be avaluated.",metric.Name));
continue;
}
bool doKeysMatch = true;
for(int i = 0; i < publicKey.Length; i++)
{
if(publicKey[i] != anticipatingMindsPublickKey[i])
{
if(publicKey == null || publicKey.Length == 0 || publicKey.Length != anticipatingMindsPublickKey.Length)
{
doKeysMatch = false;
break;
}
}
}
if(!doKeysMatch)
{
GetOutputWindow().OutputString(String.Format("Metric {0} will not be avaluated.",metric.Name));
continue;
}
metricsList.Add(metric);
}
metrics = metricsList.ToArray(typeof(CodeMetric)) as CodeMetric[];
}
MeasurementDataSet dataSet = CodeInsight.CodeMeasurements.GetCodeMeasurements(codeSolution,CodeInsight.CodeMeasurements.Metrics);
GetOutputWindow().OutputString("\n");
GetOutputWindow().OutputString("Building measurements report...");
GetOutputWindow().OutputString("\n");
applicationObject.StatusBar.Progress(true,"Building measurements report...",95,100);
string outputXmlFileName = Path.Combine(Path.GetTempPath(),"CodeMeasurements.xml");
string outputHtmlFileName = Path.Combine(Path.GetTempPath(),"CodeMeasurements.html");
if(File.Exists(outputHtmlFileName))
File.Delete(outputHtmlFileName);
if(File.Exists(outputXmlFileName))
File.Delete(outputXmlFileName);
foreach(DataTable table in dataSet.Tables)
foreach(DataRelation r in table.ChildRelations)
r.Nested = true;
using(TextWriter streamWriter = new StreamWriter(outputXmlFileName,false,System.Text.UnicodeEncoding.Unicode))
{
XmlTextWriter xmlTextWriter = new XmlTextWriter(streamWriter);
xmlTextWriter.Formatting = Formatting.Indented;
xmlTextWriter.WriteStartDocument(true);
dataSet.WriteXml(xmlTextWriter,System.Data.XmlWriteMode.WriteSchema);
xmlTextWriter.WriteEndDocument();
xmlTextWriter.Flush();
xmlTextWriter.Close();
}
XslTransform xslTransform = new XslTransform();
xslTransform.Load(devMetricsAddInConfiguration.GetInstance().HtmlXsltFileName);
xslTransform.Transform(outputXmlFileName,outputHtmlFileName);
File.Delete(outputXmlFileName);
if(devMetricsAddInConfiguration.GetInstance().ShowReportInExternalWindow)
{
System.Diagnostics.Process.Start( outputHtmlFileName );
}
else
{
applicationObject.ItemOperations.Navigate( outputHtmlFileName, EnvDTE.vsNavigateOptions.vsNavigateOptionsDefault );
}
GetOutputWindow().OutputString("Done.");
GetOutputWindow().OutputString("\n");
applicationObject.StatusBar.Progress(false,"",100,100);
}
catch (Exception e)
{
ExceptionsManager.ReportUnhandledException(e, "devMetrics");
}
}
finally
{
isAnalyzing = false;
}
}
private CodeSolution BuildCodeModel()
{
ManualResetEvent userRequestedCancelEvent = new ManualResetEvent(false);
GetOutputWindow().Clear();
GetOutputWindow().Activate();
GetOutputWindow().OutputString(GetLocalizedString("Analysis_SolutionParsingStarted",applicationObject.Solution.FullName));
GetOutputWindow().OutputString("\n\n");
CodeSolution codeSolution = new CodeSolution(Path.GetFileNameWithoutExtension(applicationObject.Solution.FileName));
foreach(Project project in vsProjectModel.ProjectsBuildOrder)
{
if(project.Kind == VSLangProj.PrjKind.prjKindCSharpProject)
{
GetOutputWindow().OutputString(GetLocalizedString("Analysis_ProjectParsingStarted",project.Name));
GetOutputWindow().OutputString("\n");
CodeAssembly codeAssembly = new CodeAssembly();
codeAssembly.AssemblyFileName = (string) project.Properties.Item("OutputFileName").Value;
codeAssembly.AssemblyName = (string) project.Properties.Item("AssemblyName").Value;
codeAssembly.Solution = codeSolution;
codeSolution.Assemblies.Add(codeAssembly);
codeAssembly.ApplicationData["VSProjectId"] = project.UniqueName;
codeAssembly.ApplicationData["ProjectFileName"] = project.FileName;
codeAssembly.ApplicationData["DefaultNamespace"] = (string) project.Properties.Item("DefaultNamespace").Value;
VSLangProj.VSProject vsProject = project.Object as VSLangProj.VSProject;
if(vsProject != null && vsProject.References != null)
{
//Let's not forget MSCORLIB.DLL that exports the most of the core types
//but somehow is not on the list of references
//Load the same assembly VS.NET has loaded for our appdomain because MSCORLIB is fused into
//VS.NET and we need to match versions for VS.NET 2003 & VS.NET 2002
codeAssembly.ReferencedAssemblies.Add(Assembly.GetAssembly(typeof(System.Exception)).Location);
foreach(VSLangProj.Reference assemblyReference in vsProject.References)
{
if(assemblyReference.Path != null && assemblyReference.Path.Length > 0)
codeAssembly.ReferencedAssemblies.Add(assemblyReference.Path);
}
}
Configuration activeConfiguration = project.ConfigurationManager.ActiveConfiguration;
string configurationName = activeConfiguration.ConfigurationName;
string[] compilationConstants = (activeConfiguration.Properties.Item("DefineConstants").Value as string).Split(';');
codeAssembly.CompilationConstants = compilationConstants;
StringCollection projectFiles = vsProjectModel.GetProjectFiles(project);
//Parse project files
int filesParsed = 0;
foreach(string fileName in projectFiles)
{
applicationObject.StatusBar.Progress(true,"Parsing project '" + vsProject.Project.Name+"'",filesParsed++,projectFiles.Count);
//Do not analyze webservice code behind files. //VS .NET tends to crash if we do so.
if(fileName.EndsWith(".asmx.cs"))
continue;
if(!File.Exists(fileName))
continue;
string code = GetFileContent(fileName);
CodeParser fileParser = GetFileParser(fileName,code,codeAssembly);
if(fileParser == null)
continue;
CodeAssemblyFile compileUnit = fileParser.Parse(userRequestedCancelEvent) as CodeAssemblyFile;
if(compileUnit != null && fileParser.Errors.Count == 0)
{
codeAssembly.AssemblyFiles.Add(compileUnit);
compileUnit.Assembly = codeAssembly;
}
foreach(System.CodeDom.Compiler.CompilerError error in fileParser.Errors)
{
GetOutputWindow().OutputString(GetLocalizedString("Analysis_ParsingError",fileName,error.Line.ToString(),error.Column.ToString(),error.ErrorText));
GetOutputWindow().OutputString("\n");
}
if(userRequestedCancelEvent.WaitOne(0,false))
return null;
}
}
}
ArrayList assemblyLinkingDoneEvents = new ArrayList();
GetOutputWindow().OutputString(GetLocalizedString("Analysis_AssemblyLinkingStarted"));
GetOutputWindow().OutputString("\n");
//Now, after we finished building codemodel, link the assemblies metadata
foreach(CodeAssembly codeAssembly in codeSolution.Assemblies)
{
BuildAssemblyMetadataThreadData data = new BuildAssemblyMetadataThreadData();
data.codeAssembly = codeAssembly;
data.devMetrics = this;
data.userRequestedCancelEvent = new ManualResetEvent(false);
data.assemblyLinkingDoneEvent = new ManualResetEvent(false);
assemblyLinkingDoneEvents.Add(data.assemblyLinkingDoneEvent);
while(!ThreadPool.QueueUserWorkItem(new WaitCallback(BuildAssemblyMetadata),data))
System.Threading.Thread.Sleep(100);
//Wait handle does not support more than 64 elements
//but lets limit ourselves to 16 elements so we do not overeat resources.
if(assemblyLinkingDoneEvents.Count > 15)
{
WaitHandle.WaitAll(assemblyLinkingDoneEvents.ToArray(typeof(ManualResetEvent)) as ManualResetEvent[]);
assemblyLinkingDoneEvents.Clear();
}
}
//Force release of memory allocated during parsing.
if(assemblyLinkingDoneEvents.Count != 0)
{
WaitHandle.WaitAll(assemblyLinkingDoneEvents.ToArray(typeof(ManualResetEvent)) as ManualResetEvent[]);
assemblyLinkingDoneEvents.Clear();
}
return codeSolution;
}
private static void BuildAssemblyMetadata(object buildAssemblyMetadataThreadData)
{
BuildAssemblyMetadataThreadData data = buildAssemblyMetadataThreadData as BuildAssemblyMetadataThreadData;
try
{
data.codeAssembly.TypeManager.BuildAssemblyMetadata(data.userRequestedCancelEvent);
lock(BuildAssemblyMetadataThreadData.outputLock)
{
data.devMetrics.GetOutputWindow().OutputString(GetLocalizedString("Analysis_ProjectLinkingEnded",data.codeAssembly.AssemblyName));
data.devMetrics.GetOutputWindow().OutputString("\n");
}
}
finally
{
data.assemblyLinkingDoneEvent.Set();
}
}
private OutputWindowPane codementorOutputPane = null;
private OutputWindowPane GetOutputWindow()
{
if(codementorOutputPane == null)
{
OutputWindow outputWindow = applicationObject.Windows.Item(EnvDTE.Constants.vsWindowKindOutput).Object as OutputWindow;
int panesQty = outputWindow.OutputWindowPanes.Count;
for(int paneIndex = 0; paneIndex < panesQty; paneIndex++)
{
string paneName = outputWindow.OutputWindowPanes.Item(paneIndex+1).Name;
if(paneName == "devMetrics")
{
codementorOutputPane = outputWindow.OutputWindowPanes.Item(paneIndex);
return codementorOutputPane;
}
}
codementorOutputPane = outputWindow.OutputWindowPanes.Add("devMetrics");
}
return codementorOutputPane;
}
public void TearDownMenus()
{
foreach(Command command in applicationObject.Commands)
{
if(command == null || command.Name == null)
continue;
if(command.Name.StartsWith("devMetricsAddIn.Connect"))
{
try
{
command.Delete();
}
catch(Exception e)
{
Debug.WriteLine(e.ToString());
}
}
}
}
private string GetFileContent(string fileName)
{
Debug.Assert(fileName != null && fileName.Length > 0,"File name should not be equal null or be empty.");
string code = string.Empty;
bool isFileOpen = applicationObject.ItemOperations.IsFileOpen(fileName,Constants.vsViewKindTextView);
if(isFileOpen)
{
foreach(Document doc in applicationObject.Documents)
{
if(string.Compare(doc.FullName,fileName,true) == 0)
{
if(!doc.Saved)
{
TextDocument textDocument = doc.Object("TextDocument") as TextDocument;
TextPoint startPoint = textDocument.StartPoint;
TextPoint endPointPoint = textDocument.EndPoint;
EditPoint startEditPoint = startPoint.CreateEditPoint();
EditPoint endEditPointPoint = endPointPoint.CreateEditPoint();
code = startEditPoint.GetText(endEditPointPoint);
break;
}
else
{
using (StreamReader textReader = new StreamReader(fileName,System.Text.Encoding.Default,true))
{
code = textReader.ReadToEnd();
break;
}
}
}
}
}
else
{
using (StreamReader textReader = new StreamReader(fileName,System.Text.Encoding.Default,true))
{
code = textReader.ReadToEnd();
}
}
return code;
}
private CodeParser GetFileParser(string fileName,string sourceCode,CodeAssembly codeAssembly)
{
if(fileName.EndsWith(".cs"))
return new CSharpParser(sourceCode,fileName,codeAssembly);
if(fileName.EndsWith(".resx"))
return new ResourceParser(sourceCode,fileName,codeAssembly);
if(fileName.EndsWith(".aspx") || fileName.EndsWith(".ascx"))
return new AspNetParser(sourceCode,fileName,codeAssembly);
return null;
}
public static string GetLocalizedString(string resourceName,params string[] arguments)
{
return assemblyResourceManager.GetFormatedString(resourceName,arguments);
}
}
}
|