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();
}
}
}