public static void MergeRelatedClasses(List <HashSet <string> > classesRelatedToEachOther, SortedDictionary <Tuple <string, string>, HashSet <string> > data)
        {
            Dictionary <string, Tuple <string, string> > classNameAlreadyVisitedToKey = new Dictionary <string, Tuple <string, string> >();
            List <Tuple <string, string> > entriesToRemoveFromDictionary = new List <Tuple <string, string> >();                                            // This is here because we cannot remove the keys while iterating in the dictionary, so we do it afterwards.
            Dictionary <Tuple <string, string>, HashSet <string> > entriesToAddToDictionary = new Dictionary <Tuple <string, string>, HashSet <string> >(); // This is here because we cannot add entries while iterating in the dictionary, so we do it afterwards.

            foreach (KeyValuePair <Tuple <string, string>, HashSet <string> > pair in data)
            {
                string           theFullMethodName = pair.Key.Item1;
                string           theAssemblyName   = pair.Key.Item2;
                HashSet <string> whereItIsUsed     = pair.Value;

                // Get the class name:
                string className = theFullMethodName;
                if (className.IndexOf('.') >= 0)
                {
                    className = className.Substring(0, className.IndexOf('.'));
                }
                if (className.IndexOf(' ') >= 0)
                {
                    className = className.Substring(0, className.IndexOf(' '));
                }

                // Check if the class has related classes:
                HashSet <string> groupOfClassesRelatedToEachOther = null;
                string           classAlreadyVisitedToMerge       = null;
                foreach (HashSet <string> item in classesRelatedToEachOther)
                {
                    if (item.Contains(className))
                    {
                        groupOfClassesRelatedToEachOther = item;
                        break;
                    }
                }
                if (groupOfClassesRelatedToEachOther != null)
                {
                    // Check if we already visited one of those classes:
                    foreach (string className2 in groupOfClassesRelatedToEachOther)
                    {
                        if (classNameAlreadyVisitedToKey.ContainsKey(className2))
                        {
                            classAlreadyVisitedToMerge = className2;
                            break;
                        }
                    }
                }
                if (classAlreadyVisitedToMerge != null)
                {
                    // Get the first item to merge:
                    Tuple <string, string> keyOfFirstItem = classNameAlreadyVisitedToKey[classAlreadyVisitedToMerge];
                    HashSet <string>       valueOfFirstItem;
                    if (data.ContainsKey(keyOfFirstItem))
                    {
                        valueOfFirstItem = data[keyOfFirstItem];
                    }
                    else if (entriesToAddToDictionary.ContainsKey(keyOfFirstItem))
                    {
                        valueOfFirstItem = entriesToAddToDictionary[keyOfFirstItem];
                    }
                    else
                    {
                        throw new KeyNotFoundException("Cannot find item with key: " + keyOfFirstItem);
                    }

                    // Get the second item to merge:
                    Tuple <string, string> keyOfSecondItem   = pair.Key;
                    HashSet <string>       valueOfSecondItem = pair.Value;

                    // Merge the two classes (only if they are in the same assembly):
                    if (keyOfFirstItem.Item2 == keyOfSecondItem.Item2) // Note: "item2" is the assembly name.
                    {
                        entriesToRemoveFromDictionary.Add(keyOfFirstItem);
                        entriesToRemoveFromDictionary.Add(keyOfSecondItem);
                        string assemblyName = keyOfFirstItem.Item2;
                        HashSetHelpers.AddItemsFromOneHashSetToAnother(valueOfFirstItem, valueOfSecondItem);
                        Tuple <string, string> newKey = new Tuple <string, string>(keyOfFirstItem.Item1 + ", " + keyOfSecondItem.Item1, assemblyName);
                        entriesToAddToDictionary.Add(newKey, valueOfSecondItem);
                        classNameAlreadyVisitedToKey[classAlreadyVisitedToMerge] = newKey;
                    }
                }
                else
                {
                    classNameAlreadyVisitedToKey[className] = pair.Key;
                }
            }

            // Perform the actual addition and removal (note: this cannot be done before because it is not possible to remove or add an item from a collection while iterating it with "foreach");
            foreach (var entry in entriesToAddToDictionary)
            {
                data.Add(entry.Key, entry.Value);
            }
            foreach (var key in entriesToRemoveFromDictionary)
            {
                data.Remove(key);
            }
        }
예제 #2
0
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            string coreAssemblyPath      = CoreAssemblyPathTextBox.Text;
            string supportedElementsPath = SupportedElementsPathTextBox.Text;
            string mscorlibFolderPath    = MscorlibFolderPathTextBox.Text;
            string sdkFolderPath         = SDKFolderPathTextBox.Text;
            string otherFoldersPath      = OtherFoldersPathTextBox.Text;

            HashSet <string> xamlFilesToIgnore = null;

            try
            {
                xamlFilesToIgnore = new HashSet <string>(XamlFilesToIgnoreTextBox.Text.Split(',').Select(s => s.Trim().ToLower()));
            }
            catch (Exception ex)
            {
                MessageBox.Show("Invalid list of XAML files to exclude." + Environment.NewLine + Environment.NewLine + ex.ToString());
                return;
            }

            if (string.IsNullOrEmpty(coreAssemblyPath) ||
                string.IsNullOrEmpty(supportedElementsPath) ||
                string.IsNullOrEmpty(mscorlibFolderPath))
            {
                MessageBox.Show("Please fill all the fields before continuing.");
                return;
            }

            if (!File.Exists(supportedElementsPath))
            {
                MessageBox.Show("File not found: " + supportedElementsPath);
                return;
            }

            var featuresAndEstimationsFileProcessor = new FeaturesAndEstimationsFileProcessor(FeaturesAndEstimationsPathTextBox.Text);

            // Save the content of the text boxes for reuse when relaunching the application:
            SaveContentOfTextBoxes();

#if ASK_USER_TO_CHOOSE_ASSEMBLIES_TO_ANALYZE
            // Create OpenFileDialog
            Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

            // Set filter for file extension and default file extension
            dlg.DefaultExt  = ".dll";
            dlg.Filter      = "Assemblies and executables (*.dll,*.exe)|*.dll;*.exe";
            dlg.Multiselect = true;

            // Display OpenFileDialog by calling ShowDialog method
            Nullable <bool> result = dlg.ShowDialog();

            if (result == true)
            {
                string[] fileNames = dlg.FileNames;
#else
            string[] fileNames = Configuration.AssembliesToAnalyze;

            if (true)
            {
#endif

                if (_coreSupportedMethods == null)
                {
                    _coreSupportedMethods = new CoreSupportedMethodsContainer(coreAssemblyPath);
                }

                var logger = new LoggerThatAggregatesAllErrors();
                var watch  = Stopwatch.StartNew();
                var listOfUnsupportedMethods = new List <UnsupportedMethodInfo>();

                // Get the path of the folder where the first assembly is located, and add that path to the list of places where to look in for resolving referenced assemblies:
                string firstFileToAnalyze = fileNames.FirstOrDefault();
                string additionalFolderWhereToResolveAssemblies = null;
                if (firstFileToAnalyze != null)
                {
                    additionalFolderWhereToResolveAssemblies = System.IO.Path.GetDirectoryName(firstFileToAnalyze);
                }

                // Do the analysis:
                try
                {
                    foreach (var filename in fileNames)
                    {
                        CompatibilityAnalyzer.Analyze(
                            filename,
                            logger,
                            listOfUnsupportedMethods,
                            _coreSupportedMethods,
                            fileNames,
                            Configuration.UrlNamespacesThatBelongToUserCode,
                            Configuration.AttributesToIgnoreInXamlBecauseTheyAreFromBaseClasses,
                            xamlFilesToIgnore,
                            supportedElementsPath,
                            mscorlibFolderPath,
                            sdkFolderPath,
                            otherFoldersPath,
                            skipTypesWhereNoMethodIsActuallyCalled: true,
                            additionalFolderWhereToResolveAssemblies: additionalFolderWhereToResolveAssemblies);;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }

                // Replace all telerik assembly names with "http://schemas.telerik.com/2008/xaml/presentation" so as to avoid differences between namespaces from .xaml files and namespaces from .cs files:
                foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods)
                {
                    if (unsupportedMethodInfo.MethodAssemblyName.StartsWith("Telerik."))
                    {
                        unsupportedMethodInfo.MethodAssemblyName = "http://schemas.telerik.com/2008/xaml/presentation";
                    }
                }

                // Replace all Expression Blend-related namespaces with "http://schemas.microsoft.com/expression/2010/..." so as to avoid differences between namespaces from .xaml files and namespaces from .cs files:
                foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods)
                {
                    if (unsupportedMethodInfo.MethodAssemblyName.StartsWith("Microsoft.Expression.") ||
                        unsupportedMethodInfo.MethodAssemblyName.StartsWith("System.Windows.Interactivity") ||
                        unsupportedMethodInfo.MethodAssemblyName == "http://schemas.microsoft.com/expression/2010/drawing" ||
                        unsupportedMethodInfo.MethodAssemblyName == "http://schemas.microsoft.com/expression/2010/interactivity" ||
                        unsupportedMethodInfo.MethodAssemblyName == "http://schemas.microsoft.com/expression/2010/interactions"
                        )
                    {
                        unsupportedMethodInfo.MethodAssemblyName = "http://schemas.microsoft.com/expression/2010/...";
                    }
                }

                // Aggregate all MEF-related namespaces:
                foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods)
                {
                    if (unsupportedMethodInfo.MethodAssemblyName.StartsWith("System.ComponentModel.Composition."))
                    {
                        unsupportedMethodInfo.MethodAssemblyName = "System.ComponentModel.Composition";
                    }
                }

                // Aggregate all DomainServices-related namespaces:
                foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods)
                {
                    if (unsupportedMethodInfo.MethodAssemblyName.StartsWith("System.ServiceModel.DomainServices.Client."))
                    {
                        unsupportedMethodInfo.MethodAssemblyName = "System.ServiceModel.DomainServices.Client";
                    }
                }

                // Replace all the native MS UI-related assembly names with "http://schemas.microsoft.com/winfx/2006/xaml/presentation" so as to avoid differences between namespaces from .xaml files and namespaces from .cs files:
                foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods)
                {
                    if (unsupportedMethodInfo.MethodAssemblyName == "System.Windows" ||
                        unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Controls" ||
                        unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Controls.Data" ||
                        unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Browser" ||
                        unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Controls.Navigation" ||
                        unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Controls.Toolkit")
                    {
                        unsupportedMethodInfo.MethodAssemblyName = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
                    }
                }

                // Process the list of unsupported methods:
                SortedDictionary <Tuple <string, string>, HashSet <string> > unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed;
                ProcessUnsupportedMethods(listOfUnsupportedMethods, out unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed, rectifyMethodName: true);

                // Remove some stuff:
                foreach (Tuple <string, string> additionalEntryToRemove in Configuration.AdditionalEntriesToRemoveBeforeAggregation)
                {
                    unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Remove(additionalEntryToRemove);
                }

                // For classes that are not defined at all (ie. missing constructor), aggregate all the methods into the same line:
                List <Tuple <string, string> > entriesToRemoveFromDictionary = new List <Tuple <string, string> >();                                                                // This is here because we cannot remove the keys while iterating in the dictionary, so we do it afterwards.
                List <KeyValuePair <Tuple <string, string>, HashSet <string> > > entriesToAddToDictionary = new List <KeyValuePair <Tuple <string, string>, HashSet <string> > >(); // This is here because we cannot add entries while iterating in the dictionary, so we do it afterwards.
                HashSet <Tuple <string, string> > classesForWhichTheMethodsHaveAlreadyBeenAggregated      = new HashSet <Tuple <string, string> >();
                foreach (KeyValuePair <Tuple <string, string>, HashSet <string> > pair in unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed)
                {
                    string           theFullMethodName = pair.Key.Item1;
                    string           theAssemblyName   = pair.Key.Item2;
                    HashSet <string> whereItIsUsed     = pair.Value;

                    if (!Configuration.EntriesToNotAggregateWithOtherEntries.Contains(theFullMethodName))
                    {
#if ALWAYS_AGGREGATE_METHODS
                        if (theFullMethodName.Contains("."))
#else
                        if (theFullMethodName.Contains("..ctor"))
#endif
                        {
#if ALWAYS_AGGREGATE_METHODS
                            string className = theFullMethodName.Substring(0, theFullMethodName.IndexOf("."));
#else
                            string className = theFullMethodName.Substring(0, theFullMethodName.IndexOf("..ctor"));
#endif
                            if (!classesForWhichTheMethodsHaveAlreadyBeenAggregated.Contains(new Tuple <string, string>(className, theAssemblyName)))
                            {
                                classesForWhichTheMethodsHaveAlreadyBeenAggregated.Add(new Tuple <string, string>(className, theAssemblyName));
                                List <string> methodNames = new List <string>();

                                // Find all other methods that concern the same class:
                                foreach (KeyValuePair <Tuple <string, string>, HashSet <string> > pair2 in unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed)
                                {
                                    //todo-perf: optimize performance by creating a dictionary beforehand instead of nesting two iterations!

                                    string           theFullMethodName2 = pair2.Key.Item1;
                                    string           theAssemblyName2   = pair2.Key.Item2;
                                    HashSet <string> whereItIsUsed2     = pair.Value;
                                    if (!Configuration.EntriesToNotAggregateWithOtherEntries.Contains(theFullMethodName2))
                                    {
                                        if (theFullMethodName2.Contains('.') &&
                                            !theFullMethodName2.Contains("..ctor")
                                            )
                                        {
                                            string className2 = theFullMethodName2.Substring(0, theFullMethodName2.IndexOf('.'));
                                            if (className2 == className && theAssemblyName2 == theAssemblyName)
                                            {
                                                string methodName2 = theFullMethodName2.Substring(theFullMethodName2.IndexOf('.') + 1);
                                                methodNames.Add(methodName2);
                                                HashSetHelpers.AddItemsFromOneHashSetToAnother(whereItIsUsed2, whereItIsUsed);
                                                entriesToRemoveFromDictionary.Add(pair2.Key);
                                            }
                                        }
                                    }
                                }

                                // Aggregate them:
                                string newMethodName;
                                if (methodNames.Count > 0)
                                {
                                    if (theFullMethodName.Contains("..ctor"))
                                    {
                                        newMethodName = className + " (members used: " + string.Join(", ", methodNames) + ")";
                                    }
                                    else
                                    {
                                        newMethodName = className + "." + string.Join(", .", methodNames);
                                    }
                                }
                                else
                                {
                                    newMethodName = className;
                                }

                                entriesToRemoveFromDictionary.Add(pair.Key);
                                entriesToAddToDictionary.Add(new KeyValuePair <Tuple <string, string>, HashSet <string> >(
                                                                 new Tuple <string, string>(newMethodName, theAssemblyName),
                                                                 whereItIsUsed));
                            }
                        }
                    }
                }
                // Perform the actual removal and addition (note: this cannot be done before because it is not possible to remove or add an item from a collection while iterating it with "foreach");
                foreach (var key in entriesToRemoveFromDictionary)
                {
                    unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Remove(key);
                }
                foreach (var entry in entriesToAddToDictionary)
                {
                    unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Add(entry.Key, entry.Value);
                }

                //-------------------------------------
                // Merge classes that are related to each other:
                //-------------------------------------
                MergingRelatedClasses.MergeRelatedClasses(Configuration.ClassesRelatedToEachOther, unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed);

                //-------------------------------------
                // Remove elements with an empty assembly name because they are due to "clr-namespace:..." without any assembly being specified, which means that the assembly is the user assembly itself, so we should remove the entry:
                //-------------------------------------
                entriesToRemoveFromDictionary = new List <Tuple <string, string> >();
                foreach (var entry in unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed)
                {
                    if (entry.Key.Item2 == "")
                    {
                        entriesToRemoveFromDictionary.Add(entry.Key);
                    }
                }
                foreach (var key in entriesToRemoveFromDictionary)
                {
                    unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Remove(key);
                }

                //-------------------------------------
                // Remove some additional stuff:
                //-------------------------------------

                foreach (Tuple <string, string> additionalEntryToRemove in Configuration.AdditionalEntriesToRemoveAfterAggregation)
                {
                    unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Remove(additionalEntryToRemove);
                }

                //-------------------------------------
                // Save the result:
                //-------------------------------------

                // Save as Excel document:
                ExcelGenerator.Generate(
                    OutputExcelFilePathTextBox.Text,
                    featuresAndEstimationsFileProcessor,
                    unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed,
                    fileNames.Select(fileName => System.IO.Path.GetFileName(fileName)));

#if SAVE_CSV_DOCUMENT
                // Save as CSV document:
                CsvGenerator.Generate(OutputCsvFilePathTextBox.Text, unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed);
#endif

                var elapsedMs = watch.ElapsedMilliseconds;
                MessageBox.Show(string.Format("Operation completed in {0} seconds.", Math.Floor(elapsedMs / 1000d).ToString()));

                //-------------------------------------
                // Open the result:
                //-------------------------------------

                Process.Start(OutputExcelFilePathTextBox.Text);

                //-------------------------------------
                // Close this app:
                //-------------------------------------

                System.Windows.Application.Current.Shutdown();
            }
        }