using System; using System.Collections.Generic; using System.Text; using JetBrains.ActionManagement; using JetBrains.ProjectModel; using JetBrains.ReSharper.CodeInsight.Services.CSharp.Generate.Util; using JetBrains.ReSharper.CodeInsight.Services.Generate; using JetBrains.ReSharper.CodeInsight.Services.Generate.Util; using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.CSharp; using JetBrains.ReSharper.Psi.CSharp.Tree; using JetBrains.ReSharper.Psi.Resolve; using JetBrains.ReSharper.Psi.Tree; using JetBrains.ReSharper.Psi.Util; using JetBrains.ReSharper.TextControl; using JetBrains.Util; using OverridesUtil=JetBrains.ReSharper.Psi.CSharp.Util.OverridesUtil; namespace JetBrains.ReSharper.PowerToys.GenerateToString { /// /// This class is IGenerator with some page for field selection /// public class ToStringGenerator : WizardBasedGenerator { private readonly ToStringFieldSelectionPage myFieldsPage; private readonly ToStringConfirmReplacePage myReplacePage; private readonly ISolution mySolution; private readonly ITextControl myTextControl; private IClassLikeDeclaration myTypeDeclaration; public ToStringGenerator(IDataContext context) { // Get current solution, text control and extract class/struct from current caret point mySolution = context.GetData(DataConstants.SOLUTION); myTextControl = context.GetData(DataConstants.TEXT_CONTROL); if (mySolution != null && myTextControl != null) { myTypeDeclaration = SharedUtil.GetClassOrStructDeclaration(myTextControl, mySolution); if (myTypeDeclaration != null) { // If we have type declaration - create wizard pages myReplacePage = new ToStringConfirmReplacePage(myTypeDeclaration); myFieldsPage = new ToStringFieldSelectionPage(myTypeDeclaration); } } } public override bool IsApplicable { get { // No necessity to take read lock, because this method is executed under it Shell.Shell.Instance.AssertReadAccessAllowed(); if (myTypeDeclaration == null || !myTypeDeclaration.IsValid()) return false; return myTypeDeclaration.DeclaredElement != null; } } public override IEnumerable Pages { get { yield return myReplacePage; yield return myFieldsPage; } } public override string Title { get { return "Generate ToString"; } } public override void BeforePsiChange() { } public override void UnderPsiChange() { if (myTypeDeclaration == null || !myTypeDeclaration.IsValid()) return; // Save existing ToString method using element pointer IMethod oldMethod = myReplacePage.ExistingMethod; ElementInstancePointer oldMethodPointer = oldMethod == null ? null : new ElementInstancePointer(oldMethod); // Create anchor from caret position. We will add new method somewhere near caret IClassMemberDeclaration anchorDeclaration; ICSharpTypeMemberDeclarationNode anchorNode = SharedUtil.RememberAnchor(myTextControl.CaretModel.Offset, myTextControl, ref myTypeDeclaration, out anchorDeclaration); // Get ITypeElement for System.Object ITypeElement @object = PsiManager.GetInstance(mySolution).PredefinedType.Object.GetTypeElement(); foreach (IOverridableMember toString in MiscUtil.EnumerateMembers(@object, "ToString", true)) { // Loop body actually executes once, because there is only one ToString in System.Object // We create the declaration using OverridesUtil, which can create correct method signature from existing method IMethodDeclaration newToString = (IMethodDeclaration) OverridesUtil.OverridableMemberDeclaration(toString, EmptySubstitution.INSTANCE, myTypeDeclaration.DeclaredElement); // Add method to class/struct and indicate it is an override. newToString = (IMethodDeclaration)SharedUtil.AddDeclaration(newToString, myTypeDeclaration, ref anchorDeclaration, anchorNode); newToString.SetOverride(true); // Now go fill the method's body GenerateBody(newToString); } SharedUtil.ClearAnchor(anchorDeclaration, anchorNode); // If we had existing override, and user indicated he wants to remove it, restore IMethod and delete it if (oldMethodPointer != null && myReplacePage.ReplaceMethodsSelection == 0) { oldMethod = (IMethod) oldMethodPointer.DeclaredElement; if (oldMethod != null && oldMethod.IsValid()) { // Currently, method has one declaration, but with C# 3.0 partial methods it could change... foreach (IDeclaration declaration in oldMethod.GetDeclarations()) { IMethodDeclaration methodDeclaration = declaration as IMethodDeclaration; if (methodDeclaration != null) { // We must remove from method declaration's containing type declaration, not myTypeDeclaration // because existing ToString can be located in other part of the class IClassLikeDeclaration classDeclaration = (IClassLikeDeclaration) ClassLikeDeclarationNavigator.GetByMethodDeclaration(methodDeclaration); classDeclaration.RemoveClassMemberDeclaration(methodDeclaration); } } } } } public override void AfterPsiChange() { } public override ModificationCookie EnsureWritable() { // Using SharedUtil.RememberAnchor requires modification cookie for current file return myTextControl.Document.EnsureWritable(); } private void GenerateBody(IFunctionDeclaration methodDeclaration) { CSharpElementFactory factory = CSharpElementFactory.GetInstance(mySolution); // Get body IBlock body = methodDeclaration.Body; // The line below generates an InternalException IDeclaredType stringBuilderType = TypeFactory.CreateTypeByCLRName("System.Text.StringBuilder", methodDeclaration.GetProject()); body.AddStatementBefore( factory.CreateStatement("$0 builder = new $1(512);", stringBuilderType, stringBuilderType), null); body.AddStatementBefore(factory.CreateStatement("builder.Append( GetType().Name ).Append( \"[\" );"), null); // Iterate through selected fields and present them int i = 0; const string valueTemplate = "builder.Append(\"{1}{0}=\\\"\").Append({0}).Append(\"\\\"\");"; const string referenceTemplate = "builder.Append(\"{1}{0}=\\\"\").Append({0}!=null?{0}.ToString():\"(null)\").Append(\"\\\"\");"; foreach (ElementInstancePointer pointer in myFieldsPage.SelectedFields) { IField field = pointer.DeclaredElement as IField; if (field == null) continue; // add statement to present field string split = (i++ == 0 ? "" : ","); string statementText = String.Format(CLRTypeConversionUtil.IsValueType(field.Type) ? valueTemplate : referenceTemplate, field.ShortName, split); body.AddStatementBefore(factory.CreateStatement(statementText), null); } // last statement - convert StringBuilder to string body.AddStatementBefore(factory.CreateStatement("builder.Append( \"]\" );"), null); body.AddStatementBefore(factory.CreateStatement("return builder.ToString();"), null); } public override string ToString() { StringBuilder builder = new StringBuilder(512); builder.Append(GetType().Name).Append("["); builder.Append("myFieldsPage=\"").Append(myFieldsPage != null ? myFieldsPage.ToString() : "(null)").Append("\""); builder.Append(",myReplacePage=\"").Append(myReplacePage != null ? myReplacePage.ToString() : "(null)").Append("\""); builder.Append(",mySolution=\"").Append(mySolution != null ? mySolution.ToString() : "(null)").Append("\""); builder.Append(",myTextControl=\"").Append(myTextControl != null ? myTextControl.ToString() : "(null)").Append("\""); builder.Append(",myTypeDeclaration=\"").Append(myTypeDeclaration != null ? myTypeDeclaration.ToString() : "(null)").Append("\""); builder.Append("]"); return builder.ToString(); } } }