/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Linq; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; using MSBuild = Microsoft.Build.Evaluation; using OleConstants = Microsoft.VisualStudio.OLE.Interop.Constants; using VsCommands = Microsoft.VisualStudio.VSConstants.VSStd97CmdID; using VsCommands2K = Microsoft.VisualStudio.VSConstants.VSStd2KCmdID; using System.Runtime.Versioning; using System.ComponentModel.Composition; namespace Microsoft.VisualStudio.Project { public class ReferenceContainerNode : HierarchyNode, IReferenceContainer { private EventHandler onChildAdded; private EventHandler onChildRemoved; #region ctor public ReferenceContainerNode(ProjectNode root) : base(root) { this.ExcludeNodeFromScc = true; } #endregion #region Properties private static string[] supportedReferenceTypes = new string[] { ProjectFileConstants.ProjectReference, ProjectFileConstants.Reference, ProjectFileConstants.COMReference, ProjectFileConstants.WebPiReference }; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] protected virtual string[] SupportedReferenceTypes { get { return supportedReferenceTypes; } } #endregion #region overridden properties public override int SortPriority { get { return DefaultSortOrderNode.ReferenceContainerNode; } } public override int MenuCommandId { get { return VsMenus.IDM_VS_CTXT_REFERENCEROOT; } } public override Guid ItemTypeGuid { get { return VSConstants.GUID_ItemType_VirtualFolder; } } public override string Url { get { return "References"; } } public override string Caption { get { return SR.GetString(SR.ReferencesNodeName, CultureInfo.CurrentUICulture); } } private Automation.OAReferences references; public override object Object { get { if (null == references) { references = new Automation.OAReferences(this); } return references; } } #endregion #region overridden methods public override void RemoveChild(HierarchyNode node) { base.RemoveChild(node); if (node is ReferenceNode) { FireChildRemoved((ReferenceNode)node); } } /// /// Returns an instance of the automation object for ReferenceContainerNode /// /// An intance of the Automation.OAReferenceFolderItem type if succeeeded public override object GetAutomationObject() { if (this.ProjectMgr == null || this.ProjectMgr.IsClosed) { return null; } return new Automation.OAReferenceFolderItem(this.ProjectMgr.GetAutomationObject() as Automation.OAProject, this); } /// /// Disable inline editing of Caption of a ReferendeContainerNode /// /// null public override string GetEditLabel() { return null; } public override object GetIconHandle(bool open) { return this.ProjectMgr.ImageHandler.GetIconHandle(open ? (int)ProjectNode.ImageName.OpenReferenceFolder : (int)ProjectNode.ImageName.ReferenceFolder); } /// /// References node cannot be dragged. /// /// A stringbuilder. public override string PrepareSelectedNodesForClipBoard() { return null; } /// /// Not supported. /// public override int ExcludeFromProject() { return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } public override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { if (cmdGroup == VsMenus.guidStandardCommandSet97) { switch ((VsCommands)cmd) { case VsCommands.AddNewItem: case VsCommands.AddExistingItem: result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; return VSConstants.S_OK; } } else if (cmdGroup == VsMenus.guidStandardCommandSet2K) { if ((VsCommands2K)cmd == VsCommands2K.ADDREFERENCE) { result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; return VSConstants.S_OK; } } else { return (int)OleConstants.OLECMDERR_E_UNKNOWNGROUP; } return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); } public override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { if (cmdGroup == VsMenus.guidStandardCommandSet2K) { switch ((VsCommands2K)cmd) { case VsCommands2K.ADDREFERENCE: return this.ProjectMgr.AddProjectReference(); #if FALSE case VsCommands2K.ADDWEBREFERENCE: return this.ProjectMgr.AddWebReference(); #endif } } return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); } public override bool CanDeleteItem(__VSDELETEITEMOPERATION deleteOperation) { return false; } /// /// Defines whether this node is valid node for painting the refererences icon. /// /// protected override bool CanShowDefaultIcon() { return true; } #endregion #region IReferenceContainer public IList EnumReferences() { List refs = new List(); for (HierarchyNode node = this.FirstChild; node != null; node = node.NextSibling) { ReferenceNode refNode = node as ReferenceNode; if (refNode != null) { refs.Add(refNode); } } return refs; } /// /// Adds references to this container from a MSBuild project. /// public void LoadReferencesFromBuildProject(MSBuild.Project buildProject) { foreach (string referenceType in SupportedReferenceTypes) { IEnumerable refererncesGroup = this.ProjectMgr.BuildProject.GetItems(referenceType); bool isAssemblyReference = referenceType == ProjectFileConstants.Reference; // If the project was loaded for browsing we should still create the nodes but as not resolved. if (isAssemblyReference && (!ProjectMgr.BuildProject.Targets.ContainsKey(MsBuildTarget.ResolveAssemblyReferences) || this.ProjectMgr.Build(MsBuildTarget.ResolveAssemblyReferences) != MSBuildResult.Successful)) { continue; } foreach (MSBuild.ProjectItem item in refererncesGroup) { ProjectElement element = new MsBuildProjectElement(this.ProjectMgr, item); ReferenceNode node = CreateReferenceNode(referenceType, element); if (node != null) { // Make sure that we do not want to add the item twice to the ui hierarchy // We are using here the UI representation of the Node namely the Caption to find that out, in order to // avoid different representation problems. // Example : // bool found = false; for (HierarchyNode n = this.FirstChild; n != null && !found; n = n.NextSibling) { if (String.Compare(n.Caption, node.Caption, StringComparison.OrdinalIgnoreCase) == 0) { found = true; } } if (!found) { this.AddChild(node); } } } } } /// /// Adds a reference to this container using the selector data structure to identify it. /// /// data describing selected component /// Reference in case of a valid reference node has been created. Otherwise null public ReferenceNode AddReferenceFromSelectorData(VSCOMPONENTSELECTORDATA selectorData) { //Make sure we can edit the project file if (!this.ProjectMgr.QueryEditProjectFile(false)) { throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED); } //Create the reference node ReferenceNode node = null; try { node = CreateReferenceNode(selectorData); } catch (ArgumentException) { // Some selector data was not valid. } //Add the reference node to the project if we have a valid reference node if (node != null) { // This call will find if the reference is in the project and, in this case // will not add it again, so the parent node will not be set. node.AddReference(); if (null == node.Parent) { // The reference was not added, so we can not return this item because it // is not inside the project. return null; } var added = onChildAdded; if (added != null) { onChildAdded(this, new HierarchyNodeEventArgs(node)); } } return node; } public override void AddChild(HierarchyNode node) { if (node is ReferenceNode) { ((ReferenceNode)node).RefreshReference(); } base.AddChild(node); var added = onChildAdded; if (added != null) { onChildAdded(this, new HierarchyNodeEventArgs(node)); } } #if DEV11_OR_LATER public void AddReference(IVsReference pReference) { if (pReference.AlreadyReferenced == false) return; ReferenceNode node = null; if (pReference is IVsAssemblyReference) { IVsAssemblyReference assemblyReference = (IVsAssemblyReference)pReference; node = CreateAssemblyReferenceNode(assemblyReference.Name, assemblyReference.FullPath); } else if (pReference is IVsFileReference) { IVsFileReference fileReference = (IVsFileReference)pReference; node = CreateAssemblyReferenceNode(fileReference.Name, fileReference.FullPath); } else if (pReference is IVsProjectReference) { IVsProjectReference projectReference = (IVsProjectReference)pReference; //ReferenceSpecification must be relative to the current project. EnvDTE.DTE dte = (EnvDTE.DTE)this.ProjectMgr.GetService(typeof(EnvDTE.DTE)); string relativePath = CommonUtils.GetRelativeFilePath(Path.GetDirectoryName(this.ProjectMgr.Url), Path.Combine(dte.Solution.FullName, projectReference.FullPath)); VSCOMPONENTSELECTORDATA selectorData = new VSCOMPONENTSELECTORDATA() { bstrTitle = projectReference.Name, bstrFile = projectReference.FullPath, bstrProjRef = projectReference.Identity + "|" + relativePath }; node = CreateProjectReferenceNode(selectorData); ; } if (node != null && !this.ContainsReference(node)) { this.AddChild(node); } } public bool ContainsReference(IVsReference node) { if (node == null) throw new ArgumentNullException("node"); for (HierarchyNode hierarchyNode = this.FirstChild; hierarchyNode != null; hierarchyNode = hierarchyNode.NextSibling) { ReferenceNode refNode = hierarchyNode as ReferenceNode; if (refNode != null && refNode.Equals(node)) { return true; } } return false; } public bool RemoveReference(IVsReference reference) { for (HierarchyNode hierarchyNode = this.FirstChild; hierarchyNode != null; hierarchyNode = hierarchyNode.NextSibling) { ReferenceNode refNode = hierarchyNode as ReferenceNode; if (refNode != null && refNode.Equals(reference)) { refNode.Remove(false); return true; } } return false; } #endif #endregion #region virtual methods protected virtual ReferenceNode CreateReferenceNode(string referenceType, ProjectElement element) { ReferenceNode node = null; #if FALSE if(referenceType == ProjectFileConstants.COMReference) { node = this.CreateComReferenceNode(element); } else #endif if (referenceType == ProjectFileConstants.Reference) { node = this.CreateAssemblyReferenceNode(element); } else if (referenceType == ProjectFileConstants.ProjectReference) { node = this.CreateProjectReferenceNode(element); } return node; } protected virtual ReferenceNode CreateReferenceNode(VSCOMPONENTSELECTORDATA selectorData) { ReferenceNode node = null; switch (selectorData.type) { case VSCOMPONENTTYPE.VSCOMPONENTTYPE_Project: node = this.CreateProjectReferenceNode(selectorData); break; case VSCOMPONENTTYPE.VSCOMPONENTTYPE_File: // This is the case for managed assembly case VSCOMPONENTTYPE.VSCOMPONENTTYPE_ComPlus: node = this.CreateFileComponent(selectorData); break; #if FALSE case VSCOMPONENTTYPE.VSCOMPONENTTYPE_Com2: node = this.CreateComReferenceNode(selectorData); break; #endif } return node; } #endregion #region Helper functions to add references /// /// Creates a project reference node given an existing project element. /// protected virtual ProjectReferenceNode CreateProjectReferenceNode(ProjectElement element) { return new ProjectReferenceNode(this.ProjectMgr, element); } /// /// Create a Project to Project reference given a VSCOMPONENTSELECTORDATA structure /// protected virtual ProjectReferenceNode CreateProjectReferenceNode(VSCOMPONENTSELECTORDATA selectorData) { return new ProjectReferenceNode(this.ProjectMgr, selectorData.bstrTitle, selectorData.bstrFile, selectorData.bstrProjRef); } /// /// Creates an assemby or com reference node given a selector data. /// protected virtual ReferenceNode CreateFileComponent(VSCOMPONENTSELECTORDATA selectorData) { if (null == selectorData.bstrFile) { throw new ArgumentNullException("selectorData"); } // We have a path to a file, it could be anything // First see if it is a managed assembly bool tryToCreateAnAssemblyReference = true; if (File.Exists(selectorData.bstrFile)) { try { // We should not load the assembly in the current appdomain. // If we do not do it like that and we load the assembly in the current appdomain then the assembly cannot be unloaded again. // The following problems might arose in that case. // 1. Assume that a user is extending the MPF and his project is creating a managed assembly dll. // 2. The user opens VS and creates a project and builds it. // 3. Then the user opens VS creates another project and adds a reference to the previously built assembly. This will load the assembly in the appdomain had we been using Assembly.ReflectionOnlyLoadFrom. // 4. Then he goes back to the first project modifies it an builds it. A build error is issued that the assembly is used. // GetAssemblyName is assured not to load the assembly. tryToCreateAnAssemblyReference = (AssemblyName.GetAssemblyName(selectorData.bstrFile) != null); } catch (BadImageFormatException) { // We have found the file and it is not a .NET assembly; no need to try to // load it again. tryToCreateAnAssemblyReference = false; } catch (FileLoadException) { // We must still try to load from here because this exception is thrown if we want // to add the same assembly refererence from different locations. tryToCreateAnAssemblyReference = true; } } ReferenceNode node = null; if (tryToCreateAnAssemblyReference) { // This might be a candidate for an assembly reference node. Try to load it. // CreateAssemblyReferenceNode will suppress BadImageFormatException if the node cannot be created. node = this.CreateAssemblyReferenceNode(selectorData.bstrTitle, selectorData.bstrFile); } // If no node has been created try to create a com reference node. if (node == null) { if (!File.Exists(selectorData.bstrFile)) { return null; } node = ProjectMgr.CreateReferenceNodeForFile(selectorData.bstrFile); } return node; } /// /// Creates an assembly refernce node from a project element. /// protected virtual AssemblyReferenceNode CreateAssemblyReferenceNode(ProjectElement element) { AssemblyReferenceNode node = null; try { node = new AssemblyReferenceNode(this.ProjectMgr, element); } catch (ArgumentNullException e) { Trace.WriteLine("Exception : " + e.Message); } catch (FileNotFoundException e) { Trace.WriteLine("Exception : " + e.Message); } catch (BadImageFormatException e) { Trace.WriteLine("Exception : " + e.Message); } catch (FileLoadException e) { Trace.WriteLine("Exception : " + e.Message); } catch (System.Security.SecurityException e) { Trace.WriteLine("Exception : " + e.Message); } return node; } /// /// Creates an assembly reference node from a file path. /// protected virtual AssemblyReferenceNode CreateAssemblyReferenceNode(string name, string fileName) { AssemblyReferenceNode node = null; try { node = new AssemblyReferenceNode(this.ProjectMgr, name, fileName); } catch (ArgumentNullException e) { Trace.WriteLine("Exception : " + e.Message); } catch (FileNotFoundException e) { Trace.WriteLine("Exception : " + e.Message); } catch (BadImageFormatException e) { Trace.WriteLine("Exception : " + e.Message); } catch (FileLoadException e) { Trace.WriteLine("Exception : " + e.Message); } catch (System.Security.SecurityException e) { Trace.WriteLine("Exception : " + e.Message); } return node; } #endregion public event EventHandler OnChildAdded { add { onChildAdded += value; } remove { onChildAdded -= value; } } public event EventHandler OnChildRemoved { add { onChildRemoved += value; } remove { onChildRemoved -= value; } } public void FireChildRemoved(ReferenceNode referenceNode) { var removed = onChildRemoved; if (removed != null) { removed(this, new HierarchyNodeEventArgs(referenceNode)); } } } }