#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

455 lines
18 KiB

// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.AvalonEdit.Snippets;
using ICSharpCode.Core;
using ICSharpCode.NRefactory.CSharp;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Dom.Refactoring;
using ICSharpCode.SharpDevelop.Editor;
using Dom = ICSharpCode.SharpDevelop.Dom;
namespace CSharpBinding.Refactoring
{
/// <summary>
/// Interaction logic for OverrideEqualsGetHashCodeMethodsDialog.xaml
/// </summary>
public partial class OverrideEqualsGetHashCodeMethodsDialog : AbstractInlineRefactorDialog
{
IType selectedClass;
ITextAnchor startAnchor;
IMethod selectedMethod;
AstNode baseCallNode;
public OverrideEqualsGetHashCodeMethodsDialog(InsertionContext context, ITextEditor editor, ITextAnchor startAnchor, ITextAnchor endAnchor,
ITextAnchor insertionPosition, IType selectedClass, IMethod selectedMethod, AstNode baseCallNode)
: base(context, editor, insertionPosition)
{
if (selectedClass == null)
throw new ArgumentNullException("selectedClass");
InitializeComponent();
this.selectedClass = selectedClass;
this.startAnchor = startAnchor;
this.insertionEndAnchor = endAnchor;
this.selectedMethod = selectedMethod;
this.baseCallNode = baseCallNode;
addIEquatable.Content = string.Format(StringParser.Parse("${res:AddIns.SharpRefactoring.OverrideEqualsGetHashCodeMethods.AddInterface}"),
"IEquatable<" + selectedClass.Name + ">");
string otherMethod = selectedMethod.Name == "Equals" ? "GetHashCode" : "Equals";
addOtherMethod.Content = StringParser.Parse("${res:AddIns.SharpRefactoring.OverrideEqualsGetHashCodeMethods.AddOtherMethod}", new StringTagPair("otherMethod", otherMethod));
addIEquatable.IsEnabled = !selectedClass.BaseTypes.Any(
type => {
if (!type.IsGenericReturnType)
return false;
var genericType = type.CastToGenericReturnType();
var boundTo = genericType.TypeParameter.BoundTo;
if (boundTo == null)
return false;
return boundTo.Name == selectedClass.Name;
}
);
}
static int[] largePrimes = {
1000000007, 1000000009, 1000000021, 1000000033,
1000000087, 1000000093, 1000000097, 1000000103,
1000000123, 1000000181, 1000000207, 1000000223,
1000000241, 1000000271, 1000000289, 1000000297,
1000000321, 1000000349, 1000000363, 1000000403,
1000000409, 1000000411, 1000000427, 1000000433,
1000000439, 1000000447, 1000000453, 1000000459,
1000000483, 1000000513, 1000000531, 1000000579
};
static bool CanCompareEqualityWithOperator(IType type)
{
// return true for value types except float and double
// return false for reference types except string.
return type != null
&& type.FullName != "System.Single"
&& type.FullName != "System.Double"
&& (!type.IsReferenceType
|| type.FullName == "System.String");
}
static Expression TestEquality(string other, IField field)
{
if (CanCompareEqualityWithOperator(field.ReturnType)) {
return new BinaryOperatorExpression(new MemberReferenceExpression(new ThisReferenceExpression(), field.Name),
BinaryOperatorType.Equality,
new MemberReferenceExpression(new IdentifierExpression(other), field.Name));
} else {
InvocationExpression ie = new InvocationExpression(
new MemberReferenceExpression(new TypeReferenceExpression(new TypeReference("System.Object", true)), "Equals")
);
ie.Arguments.Add(new MemberReferenceExpression(new ThisReferenceExpression(), field.Name));
ie.Arguments.Add(new MemberReferenceExpression(new IdentifierExpression(other), field.Name));
return ie;
}
}
static Expression TestEquality(string other, IProperty property)
{
if (CanCompareEqualityWithOperator(property.ReturnType)) {
return new BinaryOperatorExpression(new MemberReferenceExpression(new ThisReferenceExpression(), property.Name),
BinaryOperatorType.Equality,
new MemberReferenceExpression(new IdentifierExpression(other), property.Name));
} else {
InvocationExpression ie = new InvocationExpression(
new MemberReferenceExpression(new TypeReferenceExpression(new SimpleType("System.Object")), "Equals")
);
ie.Arguments.Add(new MemberReferenceExpression(new ThisReferenceExpression(), property.Name));
ie.Arguments.Add(new MemberReferenceExpression(new IdentifierExpression(other), property.Name));
return ie;
}
}
protected override string GenerateCode(ITypeDefinition currentClass)
{
StringBuilder code = new StringBuilder();
var line = editor.Document.GetLineByOffset(startAnchor.Offset);
string indent = DocumentUtilitites.GetWhitespaceAfter(editor.Document, line.Offset);
CodeGenerator generator = language.CodeGenerator;
// if (Options.AddIEquatableInterface) {
// TODO : add IEquatable<T> to class
// IAmbience ambience = currentClass.CompilationUnit.Language.GetAmbience();
//
// IReturnType baseRType = currentClass.CompilationUnit.ProjectContent.GetClass("System.IEquatable", 1).DefaultReturnType;
//
// IClass newClass = new DefaultClass(currentClass.CompilationUnit, currentClass.FullyQualifiedName, currentClass.Modifiers, currentClass.Region, null);
//
// foreach (IReturnType type in currentClass.BaseTypes) {
// newClass.BaseTypes.Add(type);
// }
//
//
// newClass.BaseTypes.Add(new ConstructedReturnType(baseRType, new List<IReturnType>() { currentClass.DefaultReturnType }));
//
// ambience.ConversionFlags = ConversionFlags.IncludeBody;
//
// string a = ambience.Convert(currentClass);
//
// int startOffset = editor.Document.PositionToOffset(currentClass.Region.BeginLine, currentClass.Region.BeginColumn);
// int endOffset = editor.Document.PositionToOffset(currentClass.BodyRegion.EndLine, currentClass.BodyRegion.EndColumn);
//
// editor.Document.Replace(startOffset, endOffset - startOffset, a);
// }
if (Options.SurroundWithRegion) {
editor.Document.InsertNormalized(startAnchor.Offset, "#region Equals and GetHashCode implementation\n" + indent);
}
string codeForMethodBody;
if ("Equals".Equals(selectedMethod.Name, StringComparison.Ordinal)) {
IList<MethodDeclaration> equalsOverrides = CreateEqualsOverrides(currentClass);
MethodDeclaration defaultOverride = equalsOverrides.First();
equalsOverrides = equalsOverrides.Skip(1).ToList();
StringBuilder builder = new StringBuilder();
foreach (AbstractNode element in defaultOverride.Body.Children.OfType<AbstractNode>()) {
builder.Append(language.CodeGenerator.GenerateCode(element, indent + "\t"));
}
codeForMethodBody = builder.ToString().Trim();
if (addOtherMethod.IsChecked == true) {
if (equalsOverrides.Any())
code.Append(indent + "\n" + string.Join("\n", equalsOverrides.Select(item => generator.GenerateCode(item, indent))));
code.Append(indent + "\n" + generator.GenerateCode(CreateGetHashCodeOverride(currentClass), indent));
}
} else {
StringBuilder builder = new StringBuilder();
foreach (AbstractNode element in CreateGetHashCodeOverride(currentClass).Body.Children.OfType<AbstractNode>()) {
builder.Append(language.CodeGenerator.GenerateCode(element, indent + "\t"));
}
codeForMethodBody = builder.ToString().Trim();
if (addOtherMethod.IsChecked == true)
code.Append(indent + "\n" + string.Join("\n", CreateEqualsOverrides(currentClass).Select(item => generator.GenerateCode(item, indent))));
}
if (Options.AddOperatorOverloads) {
var checkStatements = new[] {
new IfElseStatement(
new InvocationExpression(
new IdentifierExpression("ReferenceEquals"),
new List<Expression>() { new IdentifierExpression("lhs"), new IdentifierExpression("rhs") }
),
new ReturnStatement(new PrimitiveExpression(true))
),
new IfElseStatement(
new BinaryOperatorExpression(
new InvocationExpression(
new IdentifierExpression("ReferenceEquals"),
new List<Expression>() { new IdentifierExpression("lhs"), new PrimitiveExpression(null) }
),
BinaryOperatorType.LogicalOr,
new InvocationExpression(
new IdentifierExpression("ReferenceEquals"),
new List<Expression>() { new IdentifierExpression("rhs"), new PrimitiveExpression(null) }
)
),
new ReturnStatement(new PrimitiveExpression(false))
)
};
BlockStatement equalsOpBody = new BlockStatement() {
Children = {
new ReturnStatement(
new InvocationExpression(
new MemberReferenceExpression(new IdentifierExpression("lhs"), "Equals"),
new List<Expression>() { new IdentifierExpression("rhs") }
)
)
}
};
if (currentClass.ClassType == Dom.ClassType.Class) {
equalsOpBody.Children.InsertRange(0, checkStatements);
}
BlockStatement notEqualsOpBody = new BlockStatement() {
Children = {
new ReturnStatement(
new UnaryOperatorExpression(
new ParenthesizedExpression(
new BinaryOperatorExpression(
new IdentifierExpression("lhs"),
BinaryOperatorType.Equality,
new IdentifierExpression("rhs")
)
),
UnaryOperatorType.Not
)
)
}
};
code.Append(indent + "\n" + generator.GenerateCode(CreateOperatorOverload(OverloadableOperatorType.Equality, currentClass, equalsOpBody), indent));
code.Append(indent + "\n" + generator.GenerateCode(CreateOperatorOverload(OverloadableOperatorType.InEquality, currentClass, notEqualsOpBody), indent));
}
if (Options.SurroundWithRegion) {
code.AppendLine(indent + "#endregion");
}
editor.Document.InsertNormalized(insertionEndAnchor.Offset, code.ToString());
return codeForMethodBody;
}
List<MethodDeclaration> CreateEqualsOverrides(IType currentClass)
{
List<MethodDeclaration> methods = new List<MethodDeclaration>();
AstType boolReference = new SimpleType("System.Boolean");
AstType objectReference = new SimpleType("System.Object", true);
MethodDeclaration method = new MethodDeclaration {
Name = "Equals",
Modifiers = Modifiers.Public | Modifiers.Override,
ReturnType = boolReference
};
method.Parameters.Add(new ParameterDeclaration(objectReference, "obj"));
method.Body = new BlockStatement();
AstType currentType = ConvertType(currentClass.DefaultReturnType);
Expression expr = null;
if (currentClass.ClassType == Dom.ClassType.Struct) {
// return obj is CurrentType && Equals((CurrentType)obj);
expr = new IsExpression(new IdentifierExpression("obj"), currentType);
expr = new ParenthesizedExpression(expr);
expr = new BinaryOperatorExpression(
expr, BinaryOperatorType.LogicalAnd,
new InvocationExpression(
new IdentifierExpression("Equals"),
new List<Expression> {
new CastExpression(currentType, new IdentifierExpression("obj"), CastType.Cast)
}));
method.Body.AddChild(new ReturnStatement(expr));
methods.Add(method);
// IEquatable implementation:
method = new MethodDeclaration {
Name = "Equals",
Modifiers = Modifiers.Public,
ReturnType = boolReference
};
method.Parameters.Add(new ParameterDeclaration(currentType, "other"));
method.Body = new BlockStatement();
} else {
method.Body.AddChild(new VariableDeclaration(
"other",
new CastExpression(currentType, new IdentifierExpression("obj")),
currentType));
method.Body.AddChild(new IfElseStatement(
new BinaryOperatorExpression(new IdentifierExpression("other"), BinaryOperatorType.Equality, new PrimitiveExpression(null, "null")),
new ReturnStatement(new PrimitiveExpression(false, "false"))));
// expr = new BinaryOperatorExpression(new ThisReferenceExpression(),
// BinaryOperatorType.ReferenceEquality,
// new IdentifierExpression("obj"));
// method.Body.AddChild(new IfElseStatement(expr, new ReturnStatement(new PrimitiveExpression(true, "true"))));
}
expr = null;
foreach (IField field in currentClass.Fields) {
if (field.IsStatic) continue;
if (expr == null) {
expr = TestEquality("other", field);
} else {
expr = new BinaryOperatorExpression(expr, BinaryOperatorType.LogicalAnd,
TestEquality("other", field));
}
}
foreach (IProperty property in currentClass.Properties) {
if (property.IsStatic || !property.IsAutoImplemented()) continue;
if (expr == null) {
expr = TestEquality("other", property);
} else {
expr = new BinaryOperatorExpression(expr, BinaryOperatorType.LogicalAnd,
TestEquality("other", property));
}
}
method.Body.AddChild(new ReturnStatement(expr ?? new PrimitiveExpression(true, "true")));
methods.Add(method);
return methods;
}
MethodDeclaration CreateGetHashCodeOverride(IClass currentClass)
{
TypeReference intReference = new TypeReference("System.Int32", true);
VariableDeclaration hashCodeVar = new VariableDeclaration("hashCode", new PrimitiveExpression(0, "0"), intReference);
MethodDeclaration getHashCodeMethod = new MethodDeclaration {
Name = "GetHashCode",
Modifier = Modifiers.Public | Modifiers.Override,
TypeReference = intReference,
Body = new BlockStatement()
};
getHashCodeMethod.Body.AddChild(new LocalVariableDeclaration(hashCodeVar));
if (currentClass.Fields.Any(f => !f.IsStatic) || currentClass.Properties.Any(p => !p.IsStatic && p.IsAutoImplemented())) {
bool usePrimeMultiplication = currentClass.ProjectContent.Language == LanguageProperties.CSharp;
BlockStatement hashCalculationBlock;
if (usePrimeMultiplication) {
hashCalculationBlock = new BlockStatement();
getHashCodeMethod.Body.AddChild(new UncheckedStatement(hashCalculationBlock));
} else {
hashCalculationBlock = getHashCodeMethod.Body;
}
int fieldIndex = 0;
foreach (IField field in currentClass.Fields) {
if (field.IsStatic) continue;
AddToBlock(hashCodeVar, getHashCodeMethod, usePrimeMultiplication, hashCalculationBlock, ref fieldIndex, field);
}
foreach (IProperty property in currentClass.Properties) {
if (property.IsStatic || !property.IsAutoImplemented()) continue;
AddToBlock(hashCodeVar, getHashCodeMethod, usePrimeMultiplication, hashCalculationBlock, ref fieldIndex, property);
}
}
getHashCodeMethod.Body.AddChild(new ReturnStatement(new IdentifierExpression(hashCodeVar.Name)));
return getHashCodeMethod;
}
void AddToBlock(VariableDeclaration hashCodeVar, MethodDeclaration getHashCodeMethod, bool usePrimeMultiplication, BlockStatement hashCalculationBlock, ref int fieldIndex, IField field)
{
Expression expr = new InvocationExpression(new MemberReferenceExpression(new IdentifierExpression(field.Name), "GetHashCode"));
if (usePrimeMultiplication) {
int prime = largePrimes[fieldIndex++ % largePrimes.Length];
expr = new AssignmentExpression(new IdentifierExpression(hashCodeVar.Name), AssignmentOperatorType.Add, new BinaryOperatorExpression(new PrimitiveExpression(prime, prime.ToString()), BinaryOperatorType.Multiply, expr));
} else {
expr = new AssignmentExpression(new IdentifierExpression(hashCodeVar.Name), AssignmentOperatorType.ExclusiveOr, expr);
}
if (IsValueType(field.ReturnType)) {
hashCalculationBlock.AddChild(new ExpressionStatement(expr));
} else {
hashCalculationBlock.AddChild(new IfElseStatement(new BinaryOperatorExpression(new IdentifierExpression(field.Name), BinaryOperatorType.ReferenceInequality, new PrimitiveExpression(null, "null")), new ExpressionStatement(expr)));
}
}
void AddToBlock(VariableDeclaration hashCodeVar, MethodDeclaration getHashCodeMethod, bool usePrimeMultiplication, BlockStatement hashCalculationBlock, ref int fieldIndex, IProperty property)
{
Expression expr = new InvocationExpression(new MemberReferenceExpression(new IdentifierExpression(property.Name), "GetHashCode"));
if (usePrimeMultiplication) {
int prime = largePrimes[fieldIndex++ % largePrimes.Length];
expr = new AssignmentExpression(new IdentifierExpression(hashCodeVar.Name), AssignmentOperatorType.Add, new BinaryOperatorExpression(new PrimitiveExpression(prime, prime.ToString()), BinaryOperatorType.Multiply, expr));
} else {
expr = new AssignmentExpression(new IdentifierExpression(hashCodeVar.Name), AssignmentOperatorType.ExclusiveOr, expr);
}
if (IsValueType(property.ReturnType)) {
hashCalculationBlock.AddChild(new ExpressionStatement(expr));
} else {
hashCalculationBlock.AddChild(new IfElseStatement(new BinaryOperatorExpression(new IdentifierExpression(property.Name), BinaryOperatorType.ReferenceInequality, new PrimitiveExpression(null, "null")), new ExpressionStatement(expr)));
}
}
OperatorDeclaration CreateOperatorOverload(OverloadableOperatorType op, IClass currentClass, BlockStatement body)
{
return new OperatorDeclaration() {
OverloadableOperator = op,
TypeReference = new TypeReference("System.Boolean", true),
Parameters = {
new ParameterDeclarationExpression(ConvertType(currentClass.DefaultReturnType), "lhs"),
new ParameterDeclarationExpression(ConvertType(currentClass.DefaultReturnType), "rhs")
},
Modifier = Modifiers.Public | Modifiers.Static,
Body = body
};
}
protected override void CancelButtonClick(object sender, System.Windows.RoutedEventArgs e)
{
base.CancelButtonClick(sender, e);
editor.Document.Insert(anchor.Offset, baseCall);
editor.Select(anchor.Offset, baseCall.Length);
}
protected override void OKButtonClick(object sender, System.Windows.RoutedEventArgs e)
{
base.OKButtonClick(sender, e);
editor.Caret.Offset = insertionEndAnchor.Offset;
}
}
}