/// <summary> /// Loads a configuration into Confeaturator. Configuration validation errors do not pop-up /// in the UI, but a list of them are returned. /// </summary> /// <param name="fileName">The .confeaturator file path containing the configuration to be loaded.</param> /// <returns>Any path with errors that were found when loading the configuration.</returns> private List <string> LoadConfiguration(string fileName) { List <string> pathWithErrors = new List <string>(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(List <string>)); FileStream fs = new FileStream(fileName, FileMode.Open); List <string> checkedNodePaths = xmlSerializer.Deserialize(fs) as List <string>; fs.Close(); foreach (string checkedNodePath in checkedNodePaths) { string path = HttpUtility.HtmlDecode(checkedNodePath); FeatureModelTreeNode node = GetNodeFromFullPath(trvFeatures.Nodes, path); if (node != null) { if (node.Kind == FeatureModelNodeKind.Optional || node.Kind == FeatureModelNodeKind.NotApply) { node.Check(); } } else { pathWithErrors.Add(path); } } return(pathWithErrors); }
// TODO: the code below can be optimized! I'm using a dumb/slow approach here checking every node. /// <summary> /// Traverses a tree looking for the node containing a given full path. /// </summary> /// <param name="nodes">The collection of nodes.</param> /// <param name="path">Path to be searched for.</param> /// <returns></returns> private FeatureModelTreeNode GetNodeFromFullPath(TreeNodeCollection nodes, string path) { FeatureModelTreeNode result = null; foreach (FeatureModelTreeNode node in nodes) { if (node.FullPath == path) { result = node; break; } } if (result == null) { foreach (FeatureModelTreeNode node in nodes) { result = GetNodeFromFullPath(node.Nodes, path); if (result != null) { break; } } } return(result); }
/// <summary> /// Gets a list of errors present in the current configuration. /// </summary> /// <returns>A list of errors present in the current configuration.</returns> private List <string> GetConfigurationErrors() { List <string> errors = new List <string>(); DTEHelper.ClearErrorsFromOutputPane(); if (this.trvFeatures.Nodes == null || this.trvFeatures.Nodes.Count == 0) { errors.Add("No features available. Ensure you feature model diagram(s) has features and Confeaturator is refreshed."); } else { FeatureModelTreeNode rootNode = this.trvFeatures.Nodes[0] as FeatureModelTreeNode; Feature feature = rootNode.FeatureModelElement as Feature; if (feature == null) { errors.Add("Root node should be a feature. Please re-check you feature model diagrams."); } else { if (rootNode.Status == FeatureModelNodeStatus.Unchecked) { string errorMessage = string.Format("Root Confeaturator node '{0}' should be checked.", feature.Name); errors.Add(errorMessage); } rootNode.LogAlternativeIntervalErrors(errors); } } return(errors); }
/// <summary> /// Adds child Confeaturator nodes to a given node. /// </summary> /// <param name="node">The node to which children should be added.</param> private void AddChildFeatureModelNodes(FeatureModelTreeNode node) { FeatureModelElement fmElement = node.FeatureModelElement; foreach (FeatureModelElement childFMElement in fmElement.SubFeatureModelElements) { node.Nodes.Add(CreateFeatureModelTreeNode(childFMElement)); } // cross-feature model logic Feature feature = fmElement as Feature; if (feature != null) { if (feature.IsReference && !addedFeatures.Contains(feature.Name)) { FeatureModel definitionFeatureModel = Util.LoadFeatureModel(DTEHelper.GetFullProjectItemPath(feature.DefinitionFeatureModelFile)); Feature definitionFeature = definitionFeatureModel.GetFeature(feature.Name); if (definitionFeature != null) { foreach (FeatureModelElement childFMElement in definitionFeature.SubFeatureModelElements) { node.Nodes.Add(CreateFeatureModelTreeNode(childFMElement)); } } } addedFeatures.Add(feature.Name); } }
/// <summary> /// Creates a Confeaturator node. /// </summary> /// <param name="fmElement">Feature model element used to create the node.</param> /// <returns>The created Confeaturator node.</returns> private FeatureModelTreeNode CreateFeatureModelTreeNode(FeatureModelElement fmElement) { FeatureModelTreeNode node = new FeatureModelTreeNode(fmElement); AddChildFeatureModelNodes(node); node.AdjustFont(trvFeatures.Font); return(node); }
/// <summary> /// Recursively logs the checked Confeaturator node paths into a list of paths. /// </summary> /// <param name="node">The node where to start the logging recursion.</param> /// <param name="checkedNodePaths">The cumulative list of strings where to log the checked node paths.</param> private void LogCheckedNodePaths(FeatureModelTreeNode node, List <string> checkedNodePaths) { if (node.IsChecked) { checkedNodePaths.Add(HttpUtility.HtmlEncode(node.FullPath)); } foreach (FeatureModelTreeNode childNode in node.Nodes) { LogCheckedNodePaths(childNode, checkedNodePaths); } }
/// <summary> /// Inovkes actions from registered Confeaturator Action Providers. /// </summary> /// <param name="sender">The event sender object.</param> /// <param name="e">The event arguments.</param> private void btnConfigureEnvironment_Click(object sender, EventArgs e) { if (this.ConfeaturatorTreeViewHasNodes) { List <string> errors = GetConfigurationErrors(); if (errors.Count > 0) { MessageBox.Show("You feature model configuration has errors, therefore Confeaturator Actions cannot be launched. Please check the Error List and fix the errors reported.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); ShowErrorsInErrorList(errors); } else { // launching Confeaturator actions registered through FeatureModelConfigureEnvironment event if (ConfeaturatorActions != null) { try { ConfeaturatorActions(this, new ConfeaturatorActionProviderEventArgs(trvFeatures.Nodes[0] as FeatureModelTreeNode, serviceProvider)); } catch (Exception ex) { MessageBox.Show("Error launching Confeaturator actions: " + ex.Message); } } // launching Confeaturator actions registered through IConfeaturatorActionProvider iterface List <ConfeaturatorActionProviderSetting> confeaturatorActionSettings = LoadConfeaturatorActionSettings(); if (confeaturatorActionSettings.Count == 0) { MessageBox.Show("No Confeaturator Action Providers are currently specified for this solution. You can edit the Confeaturator Action Providers for this solution by clicking in the \"Add/Remove Confeaturator Action Providers\" button.", "Confeaturator Actions not run", MessageBoxButtons.OK, MessageBoxIcon.Warning); } foreach (ConfeaturatorActionProviderSetting confeaturatorActionSetting in confeaturatorActionSettings) { try { string projectPath = DTEHelper.GetProjectFolderPath(); string assemblyPath = Path.Combine(projectPath, confeaturatorActionSetting.AssemblyName); //AppDomainSetup appDomainSetup = new AppDomainSetup(); // setting ShadowCopyFile to true so that we don't lock the assembly //appDomainSetup.ShadowCopyFiles = "true"; //appDomainSetup.ApplicationBase = Path.GetDirectoryName(projectPath); //tempDomain = AppDomain.CreateDomain("TempConfeaturatorDomain", null, appDomainSetup); //Assembly confeaturatorActionAssembly = tempDomain.Load(AssemblyName.GetAssemblyName(assemblyPath)); Assembly confeaturatorActionAssembly = Assembly.LoadFile(assemblyPath); IConfeaturatorActionProvider confeaturatorAction = confeaturatorActionAssembly.CreateInstance(confeaturatorActionSetting.QualifiedClassName) as IConfeaturatorActionProvider; FeatureModelTreeNode rootNode = trvFeatures.Nodes[0] as FeatureModelTreeNode; confeaturatorAction.PerformConfeaturatorAction(DTEHelper.DTE, GetSelectedFeatures()); } catch (Exception ex) { MessageBox.Show("Error launching Confeaturator actions: " + ex.Message + "\r\n\r\nYou can edit the Confeaturator Action Providers for this solution by clicking in the \"Add/Remove Confeaturator Action Providers\" button.", "Confeaturator Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } } else { Util.ShowError("Confeaturator tree view has no nodes! Please refresh it."); } }
/// <summary> /// Enables the user to check/uncheck nodes using the space keyboard key. /// </summary> /// <param name="sender">Event sender object.</param> /// <param name="e">Event arguments.</param> private void trvFeatures_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Space) { if (trvFeatures != null && trvFeatures.SelectedNode != null) { FeatureModelTreeNode selectedNode = trvFeatures.SelectedNode as FeatureModelTreeNode; selectedNode.Click(); } } }
/// <summary> /// Checks/unchecks a Confeaturator node and perform related actions, unless the /// click happened as a result collapsing/expanding the node. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void trvFeatures_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { if (!(e.Node as FeatureModelTreeNode).IsCollapsingOrExpanding) { if (e.Button == MouseButtons.Left) { FeatureModelTreeNode node = e.Node as FeatureModelTreeNode; node.Click(); } } (e.Node as FeatureModelTreeNode).IsCollapsingOrExpanding = false; }
/// <summary> /// Recursively logs included features from a given Feature Model tree node into a list of features. /// </summary> /// <param name="node">The node from where to start the recursion.</param> /// <param name="result">A cumulative list of included features.</param> private void LogIncludedFeatures(FeatureModelTreeNode node, List <Feature> result) { Feature feature = node.FeatureModelElement as Feature; if (feature != null && node.IsPartOfConfiguration) { result.Add(feature); } foreach (FeatureModelTreeNode childNode in node.Nodes) { LogIncludedFeatures(childNode, result); } }
/// <summary> /// Appends the Details table to the report to the specified StringBuilder. /// </summary> /// <param name="htmlBuilder">The HTML string builder</param> /// <param name="root">The root Confeaturator node.</param> /// <param name="configurations">List of configurations.</param> internal static void AppendTable(StringBuilder htmlBuilder, FeatureModelTreeNode root, List<Configuration> configurations) { htmlBuilder.Append("<h2>Details</h2>"); htmlBuilder.Append("<table border='1' cellpadding='5'>"); htmlBuilder.Append("<tr style='background-color:wheat'><th>Features</th>"); foreach (Configuration configuration in configurations) { htmlBuilder.Append("<th>" + configuration.ProductName + "</th>"); } // no Total column if this report is being generated for only one configuration. if (configurations.Count > 1) { htmlBuilder.Append("<th>Total</th></tr>"); } AppendRow(htmlBuilder, root, configurations, 0); }
/// <summary> /// Appends the Details table to the report to the specified StringBuilder. /// </summary> /// <param name="htmlBuilder">The HTML string builder</param> /// <param name="root">The root Confeaturator node.</param> /// <param name="configurations">List of configurations.</param> internal static void AppendTable(StringBuilder htmlBuilder, FeatureModelTreeNode root, List <Configuration> configurations) { htmlBuilder.Append("<h2>Details</h2>"); htmlBuilder.Append("<table border='1' cellpadding='5'>"); htmlBuilder.Append("<tr style='background-color:wheat'><th>Features</th>"); foreach (Configuration configuration in configurations) { htmlBuilder.Append("<th>" + configuration.ProductName + "</th>"); } // no Total column if this report is being generated for only one configuration. if (configurations.Count > 1) { htmlBuilder.Append("<th>Total</th></tr>"); } AppendRow(htmlBuilder, root, configurations, 0); }
/// <summary> /// Generates a configuration report from a one or more configurations. If the number of configurations /// is more than one, an additional "Total" column gets generated in the detailed report. /// </summary> /// <param name="outputFileName">The output HTML file name.</param> /// <param name="root">The root Confeaturator node.</param> /// <param name="configurations">The set of configurations from which the report will be generated.</param> internal static void GenerateReport(string outputFileName, FeatureModelTreeNode root, List<Configuration> configurations) { InitializeCounters(); string configurationName = Path.GetFileNameWithoutExtension(outputFileName); StringBuilder htmlStart = new StringBuilder(); StringBuilder htmlTable = new StringBuilder(); AppendStart(htmlStart, configurations); AppendTable(htmlTable, root, configurations); AppendEnd(htmlTable); AppendSummary(htmlStart); htmlStart.Append(htmlTable.ToString()); StreamWriter sr = new StreamWriter(outputFileName); sr.Write(htmlStart.ToString()); sr.Close(); }
/// <summary> /// Generates a configuration report from a one or more configurations. If the number of configurations /// is more than one, an additional "Total" column gets generated in the detailed report. /// </summary> /// <param name="outputFileName">The output HTML file name.</param> /// <param name="root">The root Confeaturator node.</param> /// <param name="configurations">The set of configurations from which the report will be generated.</param> internal static void GenerateReport(string outputFileName, FeatureModelTreeNode root, List <Configuration> configurations) { InitializeCounters(); string configurationName = Path.GetFileNameWithoutExtension(outputFileName); StringBuilder htmlStart = new StringBuilder(); StringBuilder htmlTable = new StringBuilder(); AppendStart(htmlStart, configurations); AppendTable(htmlTable, root, configurations); AppendEnd(htmlTable); AppendSummary(htmlStart); htmlStart.Append(htmlTable.ToString()); StreamWriter sr = new StreamWriter(outputFileName); sr.Write(htmlStart.ToString()); sr.Close(); }
/// <summary> /// Creates or updates the Confeaturator tree. /// </summary> private void RefreshConfeaturatorTree() { try { SingleDiagramDocView diagramDocView = DesignerHelper.GetDiagramDocView(serviceProvider); FeatureModelDSLDiagram diagram = diagramDocView.CurrentDiagram as FeatureModelDSLDiagram; if (diagram != null) { FeatureModel featureModel = diagram.Subject as FeatureModel; if (featureModel != null) { Feature rootFeature = FeatureModel.GetCrossDiagramRootFeature(featureModel); trvFeatures.Nodes.Clear(); if (rootFeature != null) { addedFeatures = new List <string>(); FeatureModelTreeNode rootNode = CreateFeatureModelTreeNode(rootFeature); trvFeatures.Nodes.Add(rootNode); rootNode.Expand(); rootNode.Kind = FeatureModelNodeKind.Root; rootNode.Status = FeatureModelNodeStatus.CheckedAndDisabled; trvFeatures.SelectedNode = rootNode; } else { Util.ShowError("Feature model was loaded but root feature is null."); } } else { Util.ShowError("Could not load feature model into Confeaturator. Please ensure you have a valid feature model file opened."); } } else { Util.ShowError("Please have the feature model diagram you want to refresh into Confeaturator open and active in Visual Studio."); } } catch (Exception ex) { Util.ShowError("Error loading feature model into Confeaturator: " + ex.Message); } }
/// <summary> /// Generates a configuration report from a set of input configurations. /// </summary> /// <param name="ouputFileName">The HTML file where to generate the report.</param> /// <param name="inputFileNames">Input configuration (.confeaturator) files.</param> private void GenerateReport(string ouputFileName, params string[] inputFileNames) { List <Configuration> configurations = new List <Configuration>(); bool generatedWithWarnings = false; foreach (string fileName in inputFileNames) { if (LoadConfiguration(fileName).Count > 0) { generatedWithWarnings = true; } Dictionary <string, bool> configurationFeatures = new Dictionary <string, bool>(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(List <string>)); FileStream fs = new FileStream(fileName, FileMode.Open); List <string> checkedNodePaths = xmlSerializer.Deserialize(fs) as List <string>; fs.Close(); foreach (string checkedNodePath in checkedNodePaths) { FeatureModelTreeNode node = GetNodeFromFullPath(trvFeatures.Nodes, checkedNodePath); if (node != null && node.IsPartOfConfiguration) { string[] featurePathItems = checkedNodePath.Split(new[] { trvFeatures.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); string featureName = featurePathItems[featurePathItems.Length - 1]; configurationFeatures[featureName] = true; } } string configurationName = Path.GetFileNameWithoutExtension(fileName); configurations.Add(new Configuration { ProductName = configurationName, Features = configurationFeatures }); } ReportGenerator.GenerateReport(ouputFileName, trvFeatures.Nodes[0] as FeatureModelTreeNode, configurations); System.Diagnostics.Process.Start(ouputFileName); if (generatedWithWarnings) { Util.ShowWarning("Report generated, but one or more configurations seem to be out of sync with the feature model. Please re-check your configurations and save them to ensure everything is in sync."); } }
/// <summary> /// Appens a row to the Details table report to the specified StringBuilder. /// </summary> /// <param name="htmlFile">The HTML string builder.</param> /// <param name="node">The Confeaturator node from which the report row will be generated.</param> /// <param name="configurations">List of configurations.</param> /// <param name="identLevel">Identation level of the row.</param> private static void AppendRow(StringBuilder htmlFile, FeatureModelTreeNode node, List <Configuration> configurations, int identLevel) { StringBuilder blankSpaces = new StringBuilder(); for (int i = 0; i < 5 * identLevel; i++) { blankSpaces.Append(blank); } string nodeText = node.Text; Feature feature = node.FeatureModelElement as Feature; bool isFeatureNode = false; if (feature != null) { isFeatureNode = true; } if (node.Kind == FeatureModelNodeKind.NotApply || node.Kind == FeatureModelNodeKind.Optional) { nodeText = "<i>" + nodeText + "</i>"; } if (node.FeatureModelElement is Feature) { htmlFile.Append("<tr><td style='background-color:lightyellow'>" + blankSpaces + nodeText + "</td>"); } else { int colsToSpan = configurations.Count + 1; // Total # of products + feature column if (configurations.Count > 1) { colsToSpan++; // Adding "total" column } htmlFile.Append("<tr><td style='color:dimgray;background-color:lightyellow' colspan='" + colsToSpan + "'>" + blankSpaces + node.Text + "</td>"); } int timesSelected = 0; foreach (Configuration configuration in configurations) { if (isFeatureNode) { if (configuration.Features.Keys.Contains(feature.Name) && configuration.Features[feature.Name]) { htmlFile.Append("<td style='text-align:center;background-color:" + selectedBackgroundColor + "'>•</td>"); timesSelected++; } else { htmlFile.Append("<td> </td>"); } } } // "Total" column and counters if (isFeatureNode) { if (node.Kind == FeatureModelNodeKind.Root || node.Kind == FeatureModelNodeKind.Mandatory) { totalRootMandatory++; } else if (node.Kind == FeatureModelNodeKind.NotApply || node.Kind == FeatureModelNodeKind.Optional) { totalOptionalAlternative++; } if (timesSelected > 0) { if (node.Kind == FeatureModelNodeKind.Root || node.Kind == FeatureModelNodeKind.Mandatory) { totalRootMandatorySelected++; } else if (node.Kind == FeatureModelNodeKind.NotApply || node.Kind == FeatureModelNodeKind.Optional) { totalOptionalAlternativeSelected++; } // "Total" columns if (configurations.Count > 1) { htmlFile.Append("<td style='text-align:center;background-color:" + selectedBackgroundColor + "'>" + timesSelected + "</td>"); } } else { // "Total" columns if (configurations.Count > 1) { htmlFile.Append("<td style='text-align:center'>0</td>"); } } } htmlFile.Append("</tr>"); foreach (FeatureModelTreeNode childNode in node.Nodes) { AppendRow(htmlFile, childNode, configurations, identLevel + 1); } }
/// <summary> /// Creates a ConfeaturatorActionProviderEventArgs instance. /// </summary> /// <param name="rootFeatureNode">The root feature model node.</param> /// <param name="serviceProvider">A service provider.</param> public ConfeaturatorActionProviderEventArgs(FeatureModelTreeNode rootFeatureNode, IServiceProvider serviceProvider) { RootFeatureNode = rootFeatureNode; ServiceProvider = serviceProvider; DTE = serviceProvider.GetService(typeof(DTE)) as DTE; }
/// <summary> /// Appens a row to the Details table report to the specified StringBuilder. /// </summary> /// <param name="htmlFile">The HTML string builder.</param> /// <param name="node">The Confeaturator node from which the report row will be generated.</param> /// <param name="configurations">List of configurations.</param> /// <param name="identLevel">Identation level of the row.</param> private static void AppendRow(StringBuilder htmlFile, FeatureModelTreeNode node, List<Configuration> configurations, int identLevel) { StringBuilder blankSpaces = new StringBuilder(); for (int i = 0; i < 5*identLevel; i++) { blankSpaces.Append(blank); } string nodeText = node.Text; Feature feature = node.FeatureModelElement as Feature; bool isFeatureNode = false; if (feature != null) { isFeatureNode = true; } if (node.Kind == FeatureModelNodeKind.NotApply || node.Kind == FeatureModelNodeKind.Optional) { nodeText = "<i>" + nodeText + "</i>"; } if (node.FeatureModelElement is Feature) { htmlFile.Append("<tr><td style='background-color:lightyellow'>" + blankSpaces + nodeText + "</td>"); } else { int colsToSpan = configurations.Count + 1; // Total # of products + feature column if (configurations.Count > 1) { colsToSpan++; // Adding "total" column } htmlFile.Append("<tr><td style='color:dimgray;background-color:lightyellow' colspan='"+colsToSpan+"'>" + blankSpaces + node.Text + "</td>"); } int timesSelected = 0; foreach (Configuration configuration in configurations) { if (isFeatureNode) { if (configuration.Features.Keys.Contains(feature.Name) && configuration.Features[feature.Name]) { htmlFile.Append("<td style='text-align:center;background-color:" + selectedBackgroundColor + "'>•</td>"); timesSelected++; } else { htmlFile.Append("<td> </td>"); } } } // "Total" column and counters if (isFeatureNode) { if (node.Kind == FeatureModelNodeKind.Root || node.Kind == FeatureModelNodeKind.Mandatory) { totalRootMandatory++; } else if (node.Kind == FeatureModelNodeKind.NotApply || node.Kind == FeatureModelNodeKind.Optional) { totalOptionalAlternative++; } if (timesSelected > 0) { if (node.Kind == FeatureModelNodeKind.Root || node.Kind == FeatureModelNodeKind.Mandatory) { totalRootMandatorySelected++; } else if (node.Kind == FeatureModelNodeKind.NotApply || node.Kind == FeatureModelNodeKind.Optional) { totalOptionalAlternativeSelected++; } // "Total" columns if (configurations.Count > 1) { htmlFile.Append("<td style='text-align:center;background-color:" + selectedBackgroundColor + "'>" + timesSelected + "</td>"); } } else { // "Total" columns if (configurations.Count > 1) { htmlFile.Append("<td style='text-align:center'>0</td>"); } } } htmlFile.Append("</tr>"); foreach (FeatureModelTreeNode childNode in node.Nodes) { AppendRow(htmlFile, childNode, configurations, identLevel + 1); } }
/// <summary> /// Recursively logs included features from a given Feature Model tree node into a list of features. /// </summary> /// <param name="node">The node from where to start the recursion.</param> /// <param name="result">A cumulative list of included features.</param> private void LogIncludedFeatures(FeatureModelTreeNode node, List<Feature> result) { Feature feature = node.FeatureModelElement as Feature; if (feature != null && node.IsPartOfConfiguration) { result.Add(feature); } foreach (FeatureModelTreeNode childNode in node.Nodes) { LogIncludedFeatures(childNode, result); } }
/// <summary> /// Creates a Confeaturator node. /// </summary> /// <param name="fmElement">Feature model element used to create the node.</param> /// <returns>The created Confeaturator node.</returns> private FeatureModelTreeNode CreateFeatureModelTreeNode(FeatureModelElement fmElement) { FeatureModelTreeNode node = new FeatureModelTreeNode(fmElement); AddChildFeatureModelNodes(node); node.AdjustFont(trvFeatures.Font); return node; }
/// <summary> /// Recursively logs the checked Confeaturator node paths into a list of paths. /// </summary> /// <param name="node">The node where to start the logging recursion.</param> /// <param name="checkedNodePaths">The cumulative list of strings where to log the checked node paths.</param> private void LogCheckedNodePaths(FeatureModelTreeNode node, List<string> checkedNodePaths) { if (node.IsChecked) { checkedNodePaths.Add(HttpUtility.HtmlEncode(node.FullPath)); } foreach (FeatureModelTreeNode childNode in node.Nodes) { LogCheckedNodePaths(childNode, checkedNodePaths); } }