PageRenderTime 68ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/Src/MakeMethodGeneric/src/MakeMethodGenericRefactoring.cs

#
C# | 254 lines | 182 code | 31 blank | 41 comment | 23 complexity | c06b36fe0e7d33b863cd7e62448657dd MD5 | raw file
Possible License(s): CC-BY-SA-3.0, GPL-3.0
  1. /*
  2. * Copyright 2007-2011 JetBrains
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. using System.Collections.Generic;
  17. using System.Linq;
  18. using JetBrains.Application.Progress;
  19. using JetBrains.ProjectModel;
  20. using JetBrains.ReSharper.Feature.Services.OverridesSupport;
  21. using JetBrains.ReSharper.Feature.Services.Refactorings;
  22. using JetBrains.ReSharper.PowerToys.MakeMethodGeneric.Impl;
  23. using JetBrains.ReSharper.Psi;
  24. using JetBrains.ReSharper.Psi.Resolve;
  25. using JetBrains.ReSharper.Psi.Search;
  26. using JetBrains.ReSharper.Psi.Tree;
  27. using JetBrains.ReSharper.Refactorings.Common;
  28. namespace JetBrains.ReSharper.PowerToys.MakeMethodGeneric
  29. {
  30. /// <summary>
  31. /// Contains code that execute PSI transaction.
  32. /// </summary>
  33. public class MakeMethodGenericRefactoring : DrivenRefactoring<MakeMethodGenericWorkflow, MakeMethodGenericBase>
  34. {
  35. private readonly SearchDomainFactory mySearchDomainFactory;
  36. public MakeMethodGenericRefactoring(MakeMethodGenericWorkflow workFlow, ISolution solution,
  37. IRefactoringDriver driver,
  38. SearchDomainFactory searchDomainFactory) : base(workFlow, solution, driver)
  39. {
  40. mySearchDomainFactory = searchDomainFactory;
  41. }
  42. public IMethod Method { get; private set; }
  43. public IParameter Parameter { get; private set; }
  44. /// <summary>
  45. /// This code changes PSI documents. It is executed under PSI transaction, Command cookies, Reentrancy guard etc.
  46. /// All documents are committed (PSI is valid).
  47. /// </summary>
  48. public override bool Execute(IProgressIndicator pi)
  49. {
  50. pi.Start(6);
  51. //check if data stored in workflow is valid...
  52. Method = Workflow.MethodPointer.FindDeclaredElement();
  53. Parameter = Workflow.ParameterPointer.FindDeclaredElement();
  54. if (Method == null || Parameter == null)
  55. return false;
  56. IPsiServices services = Parameter.GetPsiServices();
  57. IReference[] referencesToParameter;
  58. IReference[] referencesToRootOverrides;
  59. // search for method overrides (OverridesFinder is util class that
  60. // allows to find all overrides and problems with quasi implementations)
  61. OverridesFinder overridesFinder = OverridesFinder.CreateInstance(Method);
  62. using (var subPi = new SubProgressIndicator(pi, 1))
  63. overridesFinder.Find(subPi);
  64. JetHashSet<HierarchyMember> hierarchyMembers = overridesFinder.Overrides;
  65. List<IMethod> methods = ScanHierarchyConflicts(hierarchyMembers).ToList();
  66. List<IParameter> parameters = GetAllParameters(methods).ToList();
  67. // find parameters and methods usages...
  68. using (var subPi = new SubProgressIndicator(pi, 1))
  69. {
  70. subPi.TaskName = "Searching parameter usages:";
  71. IEnumerable<IPsiSourceFile> projectFiles = from param in parameters
  72. let projectFilesOfOneParameter = param.GetSourceFiles()
  73. from projectFile in projectFilesOfOneParameter
  74. select projectFile;
  75. ISearchDomain searchDomain = mySearchDomainFactory.CreateSearchDomain(projectFiles.ToList());
  76. referencesToParameter = services.Finder.FindReferences(parameters, searchDomain, subPi);
  77. }
  78. using (var subPi = new SubProgressIndicator(pi, 1))
  79. {
  80. subPi.TaskName = "Searching method usages:";
  81. referencesToRootOverrides = services.Finder.FindReferences(methods,
  82. mySearchDomainFactory.CreateSearchDomain(Solution,
  83. false), subPi);
  84. }
  85. // this step processes method usages, removes argument and stores reference specific data to the 'MethodInvocation'.
  86. List<MethodInvocation> usages;
  87. using (var subPi = new SubProgressIndicator(pi, 1))
  88. usages = PreProcessMethodUsages(referencesToRootOverrides, subPi).ToList();
  89. // replace usages of parameters with typeof(TNewTypeParameter) expression.
  90. using (var subPi = new SubProgressIndicator(pi, 1))
  91. ProcessParameterUsages(referencesToParameter, subPi);
  92. // Remove parameters from method declarations and insert new type parameter. Map contains method -> new type parameter relation.
  93. Dictionary<IMethod, ITypeParameter> map = UpdateDeclarations(methods);
  94. // We have changed declarations. cashes should be updated)
  95. Solution.GetPsiServices().Caches.Update();
  96. // Process method usages one more time to insert correct type arguments to the call.
  97. using (var subPi = new SubProgressIndicator(pi, 1))
  98. BindUsages(usages, map, subPi);
  99. return true;
  100. }
  101. private IEnumerable<IParameter> GetAllParameters(IEnumerable<IMethod> overrides)
  102. {
  103. int index = Method.Parameters.IndexOf(Parameter);
  104. return from method in overrides
  105. let parameters = method.Parameters
  106. where index < parameters.Count
  107. select parameters[index];
  108. }
  109. private ICollection<IMethod> ScanHierarchyConflicts(IEnumerable<HierarchyMember> hierarchyMembers)
  110. {
  111. var members = new List<IMethod>();
  112. var provider = new MakeGenericHierarchyConflictTextProvider();
  113. var conflictConsumer = new HierarchyConflictConsumer();
  114. foreach (HierarchyMember hierarchyMember in hierarchyMembers)
  115. {
  116. // paranoiac check
  117. var member = hierarchyMember.Member as IMethod;
  118. if (member != null)
  119. {
  120. members.Add(member);
  121. conflictConsumer.AddConflictsForHierarhyMember(hierarchyMember);
  122. }
  123. }
  124. foreach (HierarchyConflict hierarchyConflict in conflictConsumer.MyHierarchyConflicts)
  125. {
  126. IConflict conflict = hierarchyConflict.CreateConflict(provider);
  127. if (conflict != null)
  128. Driver.AddConflict(conflict);
  129. }
  130. return members;
  131. }
  132. private void ProcessParameterUsages(ICollection<IReference> references, IProgressIndicator pi)
  133. {
  134. // can not start progress indicator with zero count.
  135. if (references.Count == 0)
  136. return;
  137. pi.Start(references.Count);
  138. foreach (IReference reference in references)
  139. {
  140. // reference can be invalid if previous changes affected it's element. It is unlikely to be occurred...
  141. if (reference.IsValid())
  142. // process reference with language specific implementation...
  143. Exec[reference.GetTreeNode().Language].ProcessParameterReference(reference);
  144. pi.Advance(1);
  145. }
  146. }
  147. private void BindUsages(ICollection<MethodInvocation> usages, IDictionary<IMethod, ITypeParameter> map,
  148. IProgressIndicator pi)
  149. {
  150. if (usages.Count == 0)
  151. return;
  152. pi.Start(usages.Count);
  153. foreach (MethodInvocation usage in usages)
  154. {
  155. if (usage.IsValid())
  156. {
  157. ITypeParameter typeParameter;
  158. if (map.TryGetValue(usage.Method, out typeParameter))
  159. {
  160. // one more call to language specific code...
  161. Exec[usage.Reference.GetTreeNode().Language].BindUsage(usage, typeParameter);
  162. }
  163. else
  164. {
  165. Exec[usage.Reference.GetTreeNode().Language].BindUsage(usage, null);
  166. }
  167. }
  168. pi.Advance(1);
  169. }
  170. }
  171. private Dictionary<IMethod, ITypeParameter> UpdateDeclarations(IEnumerable<IMethod> methods)
  172. {
  173. int index = Method.Parameters.IndexOf(Parameter);
  174. var map = new Dictionary<IMethod, ITypeParameter>();
  175. foreach (IMethod method in methods)
  176. {
  177. ITypeParameter parameter = null;
  178. foreach (IDeclaration declaration in method.GetDeclarations())
  179. {
  180. // methods can have multiple declarations (partial methods).
  181. Exec[declaration.Language].RemoveParameter(declaration, index);
  182. parameter = Exec[declaration.Language].AddTypeParameter(declaration);
  183. }
  184. if (parameter != null)
  185. {
  186. map.Add(method, parameter);
  187. }
  188. }
  189. return map;
  190. }
  191. private IEnumerable<MethodInvocation> PreProcessMethodUsages(ICollection<IReference> references,
  192. IProgressIndicator pi)
  193. {
  194. if (references.Count == 0)
  195. yield break;
  196. pi.Start(references.Count);
  197. foreach (IReference reference in references)
  198. {
  199. MethodInvocation usage = Exec[reference.GetTreeNode().Language].ProcessUsage(reference);
  200. if (usage != null)
  201. yield return usage;
  202. pi.Advance(1);
  203. }
  204. }
  205. /// <summary>
  206. /// Create language specific part of refactoring.
  207. /// Filter your language specific service and instantiate part...
  208. /// </summary>
  209. protected override MakeMethodGenericBase CreateRefactoring(IRefactoringLanguageService service)
  210. {
  211. var refactoringsLanguageService = service as PowerToyRefactoringsLanguageService;
  212. if (refactoringsLanguageService != null)
  213. {
  214. return refactoringsLanguageService.CreateMakeMethodGeneric(Workflow, Solution, Driver);
  215. }
  216. return null;
  217. }
  218. protected override MakeMethodGenericBase CreateUnsupportedRefactoring()
  219. {
  220. return new MakeMethodGenericUnsupported(Workflow, Solution, Driver);
  221. }
  222. }
  223. }