From 0ee564a95ef64ce74ea4d26084da9086198dbe68 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Fri, 9 Jan 2015 21:02:03 +0100 Subject: [PATCH] generate equals/hashCode: use velocity templates --- .../java/generate/psi/PsiAdapter.java | 19 +- .../EqualsHashCodeTemplatesManager.java | 31 +- .../generation/GenerateEqualsHelper.java | 401 ++---------------- .../codeInsight/generation/defaultEquals.vm | 95 ++++- .../codeInsight/generation/defaultHashCode.vm | 132 +++++- .../generation/ui/GenerateEqualsWizard.java | 8 +- .../generateEquals/afterArraysFromJava15.java | 2 +- .../generateEquals/afterDifferentTypes.java | 73 ++++ .../afterDifferentTypesAllNotNull.java | 73 ++++ .../afterDifferentTypesNoDouble.java | 67 +++ ...rDifferentTypesSuperEqualsAndHashCode.java | 85 ++++ .../generateEquals/afterNameConflicts.java | 73 ++++ .../generateEquals/beforeDifferentTypes.java | 21 + .../beforeDifferentTypesAllNotNull.java | 21 + .../beforeDifferentTypesNoDouble.java | 20 + ...eDifferentTypesSuperEqualsAndHashCode.java | 31 ++ .../generateEquals/beforeNameConflicts.java | 21 + .../codeInsight/GenerateEquals15Test.java | 30 ++ .../codeInsight/GenerateEqualsTestCase.java | 36 +- .../psi/codeStyle/CodeStyleSettings.java | 12 + .../java/generate/GenerationUtil.java | 52 ++- .../generate/element/AbstractElement.java | 24 +- .../java/generate/element/Element.java | 3 + .../java/generate/element/ElementFactory.java | 2 +- .../java/generate/element/ElementUtils.java | 22 +- .../generate/element/GenerationHelper.java | 48 +++ 26 files changed, 989 insertions(+), 413 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypes.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesAllNotNull.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesNoDouble.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesSuperEqualsAndHashCode.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/afterNameConflicts.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypes.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesAllNotNull.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesNoDouble.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesSuperEqualsAndHashCode.java create mode 100644 java/java-tests/testData/codeInsight/generateEquals/beforeNameConflicts.java create mode 100644 plugins/generate-tostring/src/org/jetbrains/java/generate/element/GenerationHelper.java diff --git a/java/java-analysis-impl/src/org/jetbrains/java/generate/psi/PsiAdapter.java b/java/java-analysis-impl/src/org/jetbrains/java/generate/psi/PsiAdapter.java index 4bf4f6c7d96e..75d620e1f3be 100644 --- a/java/java-analysis-impl/src/org/jetbrains/java/generate/psi/PsiAdapter.java +++ b/java/java-analysis-impl/src/org/jetbrains/java/generate/psi/PsiAdapter.java @@ -16,6 +16,8 @@ package org.jetbrains.java.generate.psi; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdkVersion; +import com.intellij.openapi.projectRoots.JavaVersionService; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; @@ -668,9 +670,16 @@ public class PsiAdapter { } public static int getJavaVersion(PsiElement element) { - final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(element); + final JavaSdkVersion sdkVersion = JavaVersionService.getInstance().getJavaSdkVersion(element); int version = 0; - switch (languageLevel) { + switch (sdkVersion) { + case JDK_1_0: + case JDK_1_1: + version = 1; + break; + case JDK_1_2: + version = 2; + break; case JDK_1_3: version = 3; break; @@ -695,4 +704,10 @@ public class PsiAdapter { } return version; } + + public static boolean isNestedArray(PsiType aType) { + if (!(aType instanceof PsiArrayType)) return false; + final PsiType componentType = ((PsiArrayType)aType).getComponentType(); + return componentType instanceof PsiArrayType; + } } diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/EqualsHashCodeTemplatesManager.java b/java/java-impl/src/com/intellij/codeInsight/generation/EqualsHashCodeTemplatesManager.java index 5c4b59d3a53e..c852e93a3db5 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/EqualsHashCodeTemplatesManager.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/EqualsHashCodeTemplatesManager.java @@ -19,6 +19,7 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.StoragePathMacros; +import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.java.generate.exception.TemplateResourceException; import org.jetbrains.java.generate.template.TemplateResource; import org.jetbrains.java.generate.template.TemplatesManager; @@ -33,11 +34,11 @@ import java.io.IOException; )} ) public class EqualsHashCodeTemplatesManager extends TemplatesManager { - private static final String DEFAULT_EQUALS = "com/intellij/codeInsight/generation/defaultEquals.vm"; - private static final String DEFAULT_HASH_CODE = "com/intellij/codeInsight/generation/defaultHashCode.vm"; + private static final String DEFAULT_EQUALS = "/com/intellij/codeInsight/generation/defaultEquals.vm"; + private static final String DEFAULT_HASH_CODE = "/com/intellij/codeInsight/generation/defaultHashCode.vm"; - public static TemplatesManager getInstance() { + public static EqualsHashCodeTemplatesManager getInstance() { return ServiceManager.getService(EqualsHashCodeTemplatesManager.class); } @@ -57,4 +58,28 @@ public class EqualsHashCodeTemplatesManager extends TemplatesManager { private static String readFile(String resourceName) throws IOException { return readFile(resourceName, EqualsHashCodeTemplatesManager.class); } + + public TemplateResource getDefaultEqualsTemplate() { + return getDefaultEqualsTemplate("equals", "hashCode"); + } + + public TemplateResource getDefaultHashcodeTemplate() { + return getDefaultEqualsTemplate("hashCode", "equals"); + } + + private TemplateResource getDefaultEqualsTemplate(String selfSuffix, String oppositeSuffix) { + final TemplateResource defaultTemplate = getDefaultTemplate(); + final String fileName = defaultTemplate.getFileName(); + if (fileName.endsWith(selfSuffix)) { + return defaultTemplate; + } + final String equalsTemplateName = StringUtil.trimEnd(fileName, oppositeSuffix) + selfSuffix; + for (TemplateResource resource : getAllTemplates()) { + if (equalsTemplateName.equals(resource.getFileName())) { + return resource; + } + } + assert false : selfSuffix + " template for " + fileName + " not found"; + return null; + } } diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/GenerateEqualsHelper.java b/java/java-impl/src/com/intellij/codeInsight/generation/GenerateEqualsHelper.java index 8cfadfd36c4a..42adc40a11dd 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/GenerateEqualsHelper.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/GenerateEqualsHelper.java @@ -15,12 +15,9 @@ */ package com.intellij.codeInsight.generation; -import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.JavaSdkVersion; -import com.intellij.openapi.projectRoots.JavaVersionService; import com.intellij.psi.*; import com.intellij.psi.codeStyle.*; import com.intellij.psi.search.GlobalSearchScope; @@ -31,8 +28,8 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NonNls; +import org.jetbrains.java.generate.GenerationUtil; -import java.text.MessageFormat; import java.util.*; /** @@ -45,16 +42,6 @@ public class GenerateEqualsHelper implements Runnable { private final PsiField[] myHashCodeFields; private final HashSet myNonNullSet; private final PsiElementFactory myFactory; - private String myParameterName; - - @NonNls private static final String BASE_OBJECT_PARAMETER_NAME = "object"; - @NonNls private static final String BASE_OBJECT_LOCAL_NAME = "that"; - @NonNls private static final String RESULT_VARIABLE = "result"; - @NonNls private static final String TEMP_VARIABLE = "temp"; - - private String myClassInstanceName; - - @NonNls private static final HashMap PRIMITIVE_HASHCODE_FORMAT = new HashMap(); private final boolean mySuperHasHashCode; private final CodeStyleManager myCodeStyleManager; private final JavaCodeStyleManager myJavaCodeStyleManager; @@ -84,28 +71,6 @@ public class GenerateEqualsHelper implements Runnable { myJavaCodeStyleManager = JavaCodeStyleManager.getInstance(manager.getProject()); } - private static String getUniqueLocalVarName(String base, PsiField[] fields) { - String id = base; - int index = 0; - while (true) { - if (index > 0) { - id = base + index; - } - index++; - boolean anyEqual = false; - for (PsiField equalsField : fields) { - if (id.equals(equalsField.getName())) { - anyEqual = true; - break; - } - } - if (!anyEqual) break; - } - - - return id; - } - private static boolean shouldAddOverrideAnnotation(PsiElement context) { CodeStyleSettings style = CodeStyleSettingsManager.getSettings(context.getProject()); @@ -165,58 +130,35 @@ public class GenerateEqualsHelper implements Runnable { private PsiMethod createEquals() throws IncorrectOperationException { - JavaCodeStyleManager codeStyleManager = myJavaCodeStyleManager; - final PsiType objectType = PsiType.getJavaLangObject(myClass.getManager(), myClass.getResolveScope()); - String[] nameSuggestions = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, objectType).names; - final String objectBaseName = nameSuggestions.length > 0 ? nameSuggestions[0] : BASE_OBJECT_PARAMETER_NAME; - myParameterName = getUniqueLocalVarName(objectBaseName, myEqualsFields); - final PsiType classType = myFactory.createType(myClass); - nameSuggestions = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, classType).names; - String instanceBaseName = nameSuggestions.length > 0 && nameSuggestions[0].length() < 10 ? nameSuggestions[0] : BASE_OBJECT_LOCAL_NAME; - myClassInstanceName = getUniqueLocalVarName(instanceBaseName, myEqualsFields); - - @NonNls StringBuffer buffer = new StringBuffer(); + @NonNls StringBuilder buffer = new StringBuilder(); CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(myProject); if (shouldAddOverrideAnnotation(myClass)) { buffer.append("@Override\n"); } - buffer.append("public boolean equals(Object ").append(myParameterName).append(") {\n"); - addEqualsPrologue(buffer); - if (myEqualsFields.length > 0) { - addClassInstance(buffer); + ArrayList equalsFields = new ArrayList(); + ContainerUtil.addAll(equalsFields, myEqualsFields); + Collections.sort(equalsFields, EqualsFieldsComparator.INSTANCE); - ArrayList equalsFields = new ArrayList(); - ContainerUtil.addAll(equalsFields, myEqualsFields); - Collections.sort(equalsFields, EqualsFieldsComparator.INSTANCE); + final HashMap contextMap = new HashMap(); - for (PsiField field : equalsFields) { - if (!field.hasModifierProperty(PsiModifier.STATIC)) { - final PsiType type = field.getType(); - if (type instanceof PsiArrayType) { - addArrayEquals(buffer, field); - } - else if (type instanceof PsiPrimitiveType) { - if (PsiType.DOUBLE.equals(type) || PsiType.FLOAT.equals(type)) { - addDoubleFieldComparison(buffer, field); - } - else { - addPrimitiveFieldComparison(buffer, field); - } - } - else { - if (type instanceof PsiClassType) { - final PsiClass aClass = ((PsiClassType)type).resolve(); - if (aClass != null && aClass.isEnum()) { - addPrimitiveFieldComparison(buffer, field); - continue; - } - } - addFieldComparison(buffer, field); - } - } - } - } - buffer.append("\nreturn true;\n}"); + final PsiType classType = JavaPsiFacade.getElementFactory(myClass.getProject()).createType(myClass); + final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(myClass.getProject()); + String[] nameSuggestions = codeStyleManager + .suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, classType).names; + String instanceBaseName = nameSuggestions.length > 0 && nameSuggestions[0].length() < 10 ? nameSuggestions[0] : "that"; + contextMap.put("instanceName", instanceBaseName); + + final PsiType objectType = PsiType.getJavaLangObject(myClass.getManager(), myClass.getResolveScope()); + nameSuggestions = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, objectType).names; + final String objectBaseName = nameSuggestions.length > 0 ? nameSuggestions[0] : "object"; + contextMap.put("baseParamName", objectBaseName); + contextMap.put("superHasEquals", superMethodExists(getEqualsSignature(myProject, myClass.getResolveScope()))); + contextMap.put("checkParameterWithInstanceof", myCheckParameterWithInstanceof); + + final String methodText = GenerationUtil + .velocityGenerateCode(myClass, equalsFields, myNonNullSet, new HashMap(), contextMap, + EqualsHashCodeTemplatesManager.getInstance().getDefaultEqualsTemplate().getTemplate(), 0, false); + buffer.append(methodText); PsiMethod result = myFactory.createMethodFromText(buffer.toString(), myClass); final PsiParameter parameter = result.getParameterList().getParameters()[0]; PsiUtil.setModifierProperty(parameter, PsiModifier.FINAL, styleSettings.GENERATE_FINAL_PARAMETERS); @@ -226,109 +168,6 @@ public class GenerateEqualsHelper implements Runnable { return method; } - private void addDoubleFieldComparison(final StringBuffer buffer, final PsiField field) { - @NonNls final String type = PsiType.DOUBLE.equals(field.getType()) ? "Double" : "Float"; - final Object[] parameters = new Object[]{type, myClassInstanceName, field.getName()}; - DOUBLE_FIELD_COMPARER_MF.format(parameters, buffer, null); - } - - @NonNls private static final MessageFormat ARRAY_COMPARER_MF = - new MessageFormat("if(!java.util.Arrays.equals({1}, {0}.{1})) return false;\n"); - - @NonNls private static final MessageFormat ARRAY_DEEP_COMPARER_MF = - new MessageFormat("if(!java.util.Arrays.deepEquals({1}, {0}.{1})) return false;\n"); - @NonNls private static final MessageFormat FIELD_COMPARER_MF = - new MessageFormat("if({1}!=null ? !{1}.equals({0}.{1}) : {0}.{1}!= null)return false;\n"); - @NonNls private static final MessageFormat NON_NULL_FIELD_COMPARER_MF = new MessageFormat("if(!{1}.equals({0}.{1}))return false;\n"); - @NonNls private static final MessageFormat PRIMITIVE_FIELD_COMPARER_MF = new MessageFormat("if({1}!={0}.{1})return false;\n"); - @NonNls private static final MessageFormat DOUBLE_FIELD_COMPARER_MF = - new MessageFormat("if({0}.compare({1}.{2}, {2}) != 0)return false;\n"); - - private void addArrayEquals(StringBuffer buffer, PsiField field) { - final PsiType fieldType = field.getType(); - if (isNestedArray(fieldType)) { - if (JavaVersionService.getInstance().isAtLeast(field, JavaSdkVersion.JDK_1_5)) { - ARRAY_DEEP_COMPARER_MF.format(getComparerFormatParameters(field), buffer, null); - } - else { - buffer.append(" "); - buffer.append(CodeInsightBundle.message("generate.equals.compare.nested.arrays.comment", field.getName())); - buffer.append("\n"); - } - return; - } - if (isArrayOfObjects(fieldType)) { - buffer.append(" "); - buffer.append(CodeInsightBundle.message("generate.equals.compare.arrays.comment")); - buffer.append("\n"); - } - - ARRAY_COMPARER_MF.format(getComparerFormatParameters(field), buffer, null); - } - - private Object[] getComparerFormatParameters(PsiField field) { - return new Object[]{myClassInstanceName, field.getName()}; - } - - - private void addFieldComparison(StringBuffer buffer, PsiField field) { - boolean canBeNull = !myNonNullSet.contains(field); - if (canBeNull) { - FIELD_COMPARER_MF.format(getComparerFormatParameters(field), buffer, null); - } - else { - NON_NULL_FIELD_COMPARER_MF.format(getComparerFormatParameters(field), buffer, null); - } - } - - private void addPrimitiveFieldComparison(StringBuffer buffer, PsiField field) { - PRIMITIVE_FIELD_COMPARER_MF.format(getComparerFormatParameters(field), buffer, null); - } - - @SuppressWarnings("HardCodedStringLiteral") - private void addInstanceOfToText(@NonNls StringBuffer buffer, String returnValue) { - if (myCheckParameterWithInstanceof) { - buffer.append("if(!(").append(myParameterName).append(" instanceof ").append(myClass.getName()) - .append(")) " + "return ").append(returnValue).append(";\n"); - } - else { - buffer.append("if(").append(myParameterName).append("== null || getClass() != ").append(myParameterName) - .append(".getClass()) " + "return ").append(returnValue).append(";\n"); - } - } - - private void addEqualsPrologue(@NonNls StringBuffer buffer) { - buffer.append("if(this=="); - buffer.append(myParameterName); - buffer.append(") return true;\n"); - if (!superMethodExists(getEqualsSignature(myProject, myClass.getResolveScope()))) { - addInstanceOfToText(buffer, Boolean.toString(false)); - } - else { - addInstanceOfToText(buffer, Boolean.toString(false)); - buffer.append("if(!super.equals("); - buffer.append(myParameterName); - buffer.append(")) return false;\n"); - } - } - - private void addClassInstance(@NonNls StringBuffer buffer) { - buffer.append("\n"); - // A a = (A) object; - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(myCodeStyleManager.getProject()); - if (settings.GENERATE_FINAL_LOCALS) { - buffer.append("final "); - } - - buffer.append(myClass.getName()); - buffer.append(" ").append(myClassInstanceName).append(" = ("); - buffer.append(myClass.getName()); - buffer.append(")"); - buffer.append(myParameterName); - buffer.append(";\n\n"); - } - - private boolean superMethodExists(MethodSignature methodSignature) { LOG.assertTrue(myClass.isValid()); PsiMethod superEquals = MethodSignatureUtil.findMethodBySignature(myClass, methodSignature, true); @@ -343,173 +182,19 @@ public class GenerateEqualsHelper implements Runnable { if (shouldAddOverrideAnnotation(myClass)) { buffer.append("@Override\n"); } - buffer.append("public int hashCode() {\n"); - if (!mySuperHasHashCode && myHashCodeFields.length == 1) { - PsiField field = myHashCodeFields[0]; - final String tempName = addTempForOneField(field, buffer); - buffer.append("return "); - if (field.getType() instanceof PsiPrimitiveType) { - addPrimitiveFieldHashCode(buffer, field, tempName); - } - else { - addFieldHashCode(buffer, field, false); - } - buffer.append(";\n}"); - } - else if (myHashCodeFields.length > 0) { - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(myCodeStyleManager.getProject()); - final String resultName = getUniqueLocalVarName(settings.LOCAL_VARIABLE_NAME_PREFIX + RESULT_VARIABLE, myHashCodeFields); - buffer.append("int "); - buffer.append(resultName); + final HashMap contextMap = new HashMap(); + contextMap.put("superHasHashCode", mySuperHasHashCode); - boolean resultAssigned = false; - boolean resultDeclarationCompleted = false; - if (mySuperHasHashCode) { - buffer.append(" = "); - addSuperHashCode(buffer); - buffer.append(";\n"); - resultAssigned = true; - resultDeclarationCompleted = true; - } - String tempName = addTempDeclaration(buffer, resultDeclarationCompleted); - if (tempName != null) { - resultDeclarationCompleted = true; - } - for (PsiField field : myHashCodeFields) { - addTempAssignment(field, buffer, tempName); - if (resultDeclarationCompleted) { - buffer.append(resultName); - } - - buffer.append(" = "); - if (resultAssigned) { - buffer.append("31*"); - buffer.append(resultName); - buffer.append(" + "); - } - if (field.getType() instanceof PsiPrimitiveType) { - addPrimitiveFieldHashCode(buffer, field, tempName); - } - else { - addFieldHashCode(buffer, field, resultAssigned); - } - buffer.append(";\n"); - resultAssigned = true; - resultDeclarationCompleted = true; - } - buffer.append("return "); - buffer.append(resultName); - buffer.append(";\n}"); - } - else { - buffer.append("return 0;\n}"); - } + final String methodText = GenerationUtil + .velocityGenerateCode(myClass, Arrays.asList(myHashCodeFields), myNonNullSet, new HashMap(), contextMap, + EqualsHashCodeTemplatesManager.getInstance().getDefaultHashcodeTemplate().getTemplate(), 0, false); + buffer.append(methodText); PsiMethod hashCode = myFactory.createMethodFromText(buffer.toString(), null); hashCode = (PsiMethod)myJavaCodeStyleManager.shortenClassReferences(hashCode); return (PsiMethod)myCodeStyleManager.reformat(hashCode); } - private static void addTempAssignment(PsiField field, StringBuilder buffer, String tempName) { - if (PsiType.DOUBLE.equals(field.getType())) { - buffer.append(tempName); - addTempForDoubleInitialization(field, buffer); - } - } - - private static void addTempForDoubleInitialization(PsiField field, @NonNls StringBuilder buffer) { - buffer.append(" = "); - buffer.append("Double.doubleToLongBits("); - buffer.append(field.getName()); - buffer.append(");\n"); - } - - private String addTempDeclaration(@NonNls StringBuilder buffer, boolean resultDeclarationCompleted) { - for (PsiField hashCodeField : myHashCodeFields) { - if (PsiType.DOUBLE.equals(hashCodeField.getType())) { - final String name = getUniqueLocalVarName(TEMP_VARIABLE, myHashCodeFields); - if (!resultDeclarationCompleted) { - buffer.append("\n;"); - } - buffer.append("long "); - buffer.append(name); - buffer.append(";\n"); - return name; - } - } - return null; - } - - @SuppressWarnings("HardCodedStringLiteral") - private String addTempForOneField(PsiField field, StringBuilder buffer) { - if (PsiType.DOUBLE.equals(field.getType())) { - final String name = getUniqueLocalVarName(TEMP_VARIABLE, myHashCodeFields); - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(myCodeStyleManager.getProject()); - if (settings.GENERATE_FINAL_LOCALS) { - buffer.append("final "); - } - buffer.append("long ").append(name); - addTempForDoubleInitialization(field, buffer); - return name; - } - else { - return null; - } - } - - private static void addPrimitiveFieldHashCode(StringBuilder buffer, PsiField field, String tempName) { - MessageFormat format = PRIMITIVE_HASHCODE_FORMAT.get(field.getType().getCanonicalText()); - buffer.append(format.format(new Object[]{field.getName(), tempName})); - } - - private void addFieldHashCode(@NonNls StringBuilder buffer, PsiField field, boolean brace) { - final String name = field.getName(); - if (myNonNullSet.contains(field)) { - adjustHashCodeToArrays(buffer, field, name); - } - else { - if (brace) { - buffer.append("("); - } - buffer.append(name); - buffer.append(" != null ? "); - adjustHashCodeToArrays(buffer, field, name); - buffer.append(" : 0"); - if (brace) { - buffer.append(")"); - } - } - } - - private static void adjustHashCodeToArrays(@NonNls StringBuilder buffer, final PsiField field, final String name) { - final PsiType fieldType = field.getType(); - if (fieldType instanceof PsiArrayType && - JavaVersionService.getInstance().isAtLeast(field, JavaSdkVersion.JDK_1_5)) { - if (isNestedArray(fieldType)) { - buffer.append(" "); - buffer.append("// Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode"); - buffer.append("\n"); - } - buffer.append("java.util.Arrays.hashCode("); - buffer.append(name); - buffer.append(")"); - } - else { - buffer.append(name); - buffer.append(".hashCode()"); - } - } - - @SuppressWarnings("HardCodedStringLiteral") - private void addSuperHashCode(StringBuilder buffer) { - if (mySuperHasHashCode) { - buffer.append("super.hashCode()"); - } - else { - buffer.append("0"); - } - } - public void invoke() { ApplicationManager.getApplication().runWriteAction(this); @@ -533,32 +218,6 @@ public class GenerateEqualsHelper implements Runnable { } } - static { - initPrimitiveHashcodeFormats(); - } - - @SuppressWarnings("HardCodedStringLiteral") - private static void initPrimitiveHashcodeFormats() { - PRIMITIVE_HASHCODE_FORMAT.put("byte", new MessageFormat("(int) {0}")); - PRIMITIVE_HASHCODE_FORMAT.put("short", new MessageFormat("(int) {0}")); - PRIMITIVE_HASHCODE_FORMAT.put("int", new MessageFormat("{0}")); - PRIMITIVE_HASHCODE_FORMAT.put("long", new MessageFormat("(int) ({0} ^ ({0} >>> 32))")); - PRIMITIVE_HASHCODE_FORMAT.put("boolean", new MessageFormat("({0} ? 1 : 0)")); - - PRIMITIVE_HASHCODE_FORMAT.put("float", new MessageFormat("({0} != +0.0f ? Float.floatToIntBits({0}) : 0)")); - PRIMITIVE_HASHCODE_FORMAT.put("double", new MessageFormat("(int) ({1} ^ ({1} >>> 32))")); - - PRIMITIVE_HASHCODE_FORMAT.put("char", new MessageFormat("(int) {0}")); - PRIMITIVE_HASHCODE_FORMAT.put("void", new MessageFormat("0")); - PRIMITIVE_HASHCODE_FORMAT.put("void", new MessageFormat("({0} ? 1 : 0)")); - } - - public static boolean isNestedArray(PsiType aType) { - if (!(aType instanceof PsiArrayType)) return false; - final PsiType componentType = ((PsiArrayType)aType).getComponentType(); - return componentType instanceof PsiArrayType; - } - public static boolean isArrayOfObjects(PsiType aType) { if (!(aType instanceof PsiArrayType)) return false; final PsiType componentType = ((PsiArrayType)aType).getComponentType(); diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/defaultEquals.vm b/java/java-impl/src/com/intellij/codeInsight/generation/defaultEquals.vm index f07eab114ffd..5c0ba0820672 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/defaultEquals.vm +++ b/java/java-impl/src/com/intellij/codeInsight/generation/defaultEquals.vm @@ -1,2 +1,93 @@ -//todo -//equals template to place here \ No newline at end of file +#set($classInstanceName = $helper.getUniqueLocalVarName($instanceName, $members, $settings)) +#set($paramName = $helper.getUniqueLocalVarName($baseParamName, $members, $settings)) +public boolean equals(## +#if ($settings.generateFinalParameters) + final ## +#end +Object $paramName){ +#addEqualsPrologue() +#if ($members.size() > 0) + + #addClassInstance() + + #foreach($field in $members) + #if (!$field.static) + #if ($field.array) + #addArrayEquals($field) + #elseif ($field.primitive) + #if ($field.double || $field.float) + #addDoubleFieldComparison($field) + #else + #addPrimitiveFieldComparison($field) + #end + #elseif ($field.enum) + #addPrimitiveFieldComparison($field) + #else + #addFieldComparison($field) + #end + #end + #end +#end + + return true; +} +## +#macro(addClassInstance) + #if ($settings.generateFinalLocals) + final ## + #end + $classname $classInstanceName = ($classname)$paramName; +#end +## +#macro(addEqualsPrologue) + if(this == $paramName) return true; + #if (!$superHasEquals) + #addInstanceOfToText() + #else + #addInstanceOfToText() + if(!super.equals($paramName)) return false; + #end +#end +## +#macro(addInstanceOfToText) + #if ($checkParameterWithInstanceof) + if(!($paramName instanceof $classname)) return false; + #else + if($paramName == null || getClass() != ${paramName}.getClass()) return false; + #end +#end +## +#macro(addPrimitiveFieldComparison $field) + if($field.name != ${classInstanceName}.$field.name) return false; +#end +## +#macro(addDoubleFieldComparison $field) + #if ($field.double) + if(Double.compare(${classInstanceName}.$field.name, $field.name) != 0)return false; + #else + if(Float.compare(${classInstanceName}.$field.name, $field.name) != 0)return false; + #end +#end +## +#macro(addFieldComparison $field) + #if ($field.notNull) + if(!${field.name}.equals(${classInstanceName}.$field.name))return false; + #else + if($field.name != null ? !${field.name}.equals(${classInstanceName}.$field.name) : ${classInstanceName}.$field.name != null)return false; + #end +#end +## +#macro(addArrayEquals $field) + #if ($field.nestedArray) + #if ($java_version > 4) + if(!java.util.Arrays.deepEquals($field.name, ${classInstanceName}.$field.name)) return false; + #else + // Compare nested arrays - values of $field.name here + #end + #break; + #end + #if ($field.objectArray) + // Probably incorrect - comparing Object[] arrays with Arrays.equals + #end + if(!java.util.Arrays.equals($field.name, ${classInstanceName}.$field.name)) return false; +#end \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/defaultHashCode.vm b/java/java-impl/src/com/intellij/codeInsight/generation/defaultHashCode.vm index bbcfca6fa125..b8e0641d6de7 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/defaultHashCode.vm +++ b/java/java-impl/src/com/intellij/codeInsight/generation/defaultHashCode.vm @@ -1,2 +1,130 @@ -//todo -//hashCode template to place here \ No newline at end of file +public int hashCode() { +#if (!$superHasHashCode && $members.size() == 1) + #set($field = $members.get(0)) + #if ($field.primitive) +#addTempForOneField($field) + return #addPrimitiveFieldHashCode($field); + #else + return #addFieldHashCode($field, false); + #end +#elseif ($members.size() > 0) + #set($resultName = $helper.getUniqueLocalVarName("result", $members, $settings)) + int $resultName ## + #set($resultAssigned = false) + #set($resultDeclarationCompleted = false) + #if ($superHasHashCode) + = #addSuperHashCode(); + #set($resultAssigned = true) + #set($resultDeclarationCompleted = true) + #end + #addTempDeclaration($resultDeclarationCompleted) + #set($tempName = "#addTempDeclaration($resultDeclarationCompleted)") + #foreach($field in $members) + #addTempAssignment($field) + #if ($resultDeclarationCompleted) + $resultName ## + #end + = ## + #if ($resultAssigned) + 31 * $resultName + ## + #end + #if ($field.primitive) + #addPrimitiveFieldHashCode($field)## + #else + #addFieldHashCode($field, $resultAssigned)## + #end + ; + #set($resultAssigned = true) + #set($resultDeclarationCompleted = true) + #end + return $resultName; +#else + return 0; +#end +} +## +#macro (addPrimitiveFieldHashCode $field) + #set ($fName = $field.name) + #if ($field.byte || $field.short || $field.char) + (int) $fName## + #elseif ($field.long) + (int)($fName ^ ($fName >>> 32))## + #elseif ($field.boolean) + ($fName ? 1 : 0)## + #elseif ($field.float) + ($fName != +0.0f ? Float.floatToIntBits($fName) : 0)## + #elseif ($field.double) + #set($tempName = "#getTempVarName()") + (int) ($tempName ^ ($tempName >>> 32))## + #elseif ($field.void) + 0## + #else + $fName## + #end +#end +## +#macro(getTempVarName) + $helper.getUniqueLocalVarName("temp", $members, $settings)## +#end +## +#macro(addTempForOneField $field) + #if ($field.double) + #if ($settings.generateFinalLocals) + final ## + #end + #set($tempName = "#getTempVarName()") + long $tempName = Double.doubleToLongBits($field.name); + #end +#end +## +#macro(adjustHashCodeToArrays $field) +#if ($field.array && $java_version > 4) +#if ($field.nestedArray) +// Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + #end + java.util.Arrays.hashCode($field.name)## + #else + ${field.name}.hashCode()## + #end +#end +## +#macro(addSuperHashCode) + #if ($superHasHashCode) + super.hashCode()## + #else + 0## + #end +#end +## +#macro(addTempDeclaration $resultDeclarationCompleted) + #foreach($member in $members) + #if ($member.double) + #if (!$resultDeclarationCompleted) + ; + #end + #set($tempName = "#getTempVarName()") + long $tempName; + #set($resultDeclarationCompleted = true) + #break + #end + #end +#end +## +#macro(addTempAssignment $field) + #if ($field.double) + #set($tempName = "#getTempVarName()") + $tempName = Double.doubleToLongBits($field.name); + #end +#end +## +#macro(addFieldHashCode $field $brace) +#set($name = $field.name) +#if ($field.notNull)#adjustHashCodeToArrays($field) + #else + #if ($brace)(## + #end + $name != null ? #adjustHashCodeToArrays($field) : 0## + #if ($brace))## + #end + #end +#end diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/ui/GenerateEqualsWizard.java b/java/java-impl/src/com/intellij/codeInsight/generation/ui/GenerateEqualsWizard.java index 315f30c1e084..1a80bc93a0eb 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/ui/GenerateEqualsWizard.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/ui/GenerateEqualsWizard.java @@ -26,7 +26,6 @@ import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.openapi.projectRoots.JavaVersionService; import com.intellij.openapi.ui.VerticalFlowLayout; import com.intellij.psi.*; -import com.intellij.psi.util.PsiUtil; import com.intellij.refactoring.classMembers.AbstractMemberInfoModel; import com.intellij.refactoring.classMembers.MemberInfoBase; import com.intellij.refactoring.classMembers.MemberInfoTooltipManager; @@ -36,6 +35,7 @@ import com.intellij.refactoring.util.classMembers.MemberInfo; import com.intellij.ui.NonFocusableCheckBox; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.java.generate.psi.PsiAdapter; import javax.swing.*; import java.awt.event.ActionEvent; @@ -269,7 +269,7 @@ public class GenerateEqualsWizard extends AbstractGenerateEqualsWizard>> 32)); + result = 31 * result + (a11 != +0.0f ? Float.floatToIntBits(a11) : 0); + temp = Double.doubleToLongBits(a12); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + (a13 != null ? a13.hashCode() : 0); + result = 31 * result + (a14 != null ? a14.hashCode() : 0); + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesAllNotNull.java b/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesAllNotNull.java new file mode 100644 index 000000000000..e20c18ec7932 --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesAllNotNull.java @@ -0,0 +1,73 @@ +import java.util.Arrays; + +class A { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + double a12; + + Object a13; + String a14; + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final A a = (A) o; + + if (a10 != a.a10) return false; + if (Float.compare(a.a11, a11) != 0) return false; + if (Double.compare(a.a12, a12) != 0) return false; + if (a7 != a.a7) return false; + if (a8 != a.a8) return false; + if (a9 != a.a9) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(a1, a.a1)) return false; + if (!a13.equals(a.a13)) return false; + if (!a14.equals(a.a14)) return false; + if (!Arrays.deepEquals(a2, a.a2)) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(a3, a.a3)) return false; + if (!Arrays.deepEquals(a4, a.a4)) return false; + if (!Arrays.equals(a5, a.a5)) return false; + if (!Arrays.deepEquals(a6, a.a6)) return false; + + return true; + } + + @Override + public int hashCode() { + int result; + long temp; + result = Arrays.hashCode(a1); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a2); + result = 31 * result + Arrays.hashCode(a3); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a4); + result = 31 * result + Arrays.hashCode(a5); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a6); + result = 31 * result + (int) a7; + result = 31 * result + (int) a8; + result = 31 * result + a9; + result = 31 * result + (int) (a10 ^ (a10 >>> 32)); + result = 31 * result + (a11 != +0.0f ? Float.floatToIntBits(a11) : 0); + temp = Double.doubleToLongBits(a12); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + a13.hashCode(); + result = 31 * result + a14.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesNoDouble.java b/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesNoDouble.java new file mode 100644 index 000000000000..9fcdd9fc3dc4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesNoDouble.java @@ -0,0 +1,67 @@ +import java.util.Arrays; + +class A { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + + Object a13; + String a14; + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final A a = (A) o; + + if (a10 != a.a10) return false; + if (Float.compare(a.a11, a11) != 0) return false; + if (a7 != a.a7) return false; + if (a8 != a.a8) return false; + if (a9 != a.a9) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(a1, a.a1)) return false; + if (!a13.equals(a.a13)) return false; + if (!a14.equals(a.a14)) return false; + if (!Arrays.deepEquals(a2, a.a2)) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(a3, a.a3)) return false; + if (!Arrays.deepEquals(a4, a.a4)) return false; + if (!Arrays.equals(a5, a.a5)) return false; + if (!Arrays.deepEquals(a6, a.a6)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(a1); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a2); + result = 31 * result + Arrays.hashCode(a3); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a4); + result = 31 * result + Arrays.hashCode(a5); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a6); + result = 31 * result + (int) a7; + result = 31 * result + (int) a8; + result = 31 * result + a9; + result = 31 * result + (int) (a10 ^ (a10 >>> 32)); + result = 31 * result + (a11 != +0.0f ? Float.floatToIntBits(a11) : 0); + result = 31 * result + a13.hashCode(); + result = 31 * result + a14.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesSuperEqualsAndHashCode.java b/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesSuperEqualsAndHashCode.java new file mode 100644 index 000000000000..00c41d714f7b --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/afterDifferentTypesSuperEqualsAndHashCode.java @@ -0,0 +1,85 @@ +import java.util.Arrays; + +class B { + @Override + public boolean equals(Object obj) { + return obj != null; + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} +class A extends B { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + double a12; + + Object a13; + String a14; + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + final A a = (A) o; + + if (a10 != a.a10) return false; + if (Float.compare(a.a11, a11) != 0) return false; + if (Double.compare(a.a12, a12) != 0) return false; + if (a7 != a.a7) return false; + if (a8 != a.a8) return false; + if (a9 != a.a9) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(a1, a.a1)) return false; + if (!a13.equals(a.a13)) return false; + if (!a14.equals(a.a14)) return false; + if (!Arrays.deepEquals(a2, a.a2)) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(a3, a.a3)) return false; + if (!Arrays.deepEquals(a4, a.a4)) return false; + if (!Arrays.equals(a5, a.a5)) return false; + if (!Arrays.deepEquals(a6, a.a6)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + long temp; + result = 31 * result + Arrays.hashCode(a1); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a2); + result = 31 * result + Arrays.hashCode(a3); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a4); + result = 31 * result + Arrays.hashCode(a5); + result = 31 * result + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a6); + result = 31 * result + (int) a7; + result = 31 * result + (int) a8; + result = 31 * result + a9; + result = 31 * result + (int) (a10 ^ (a10 >>> 32)); + result = 31 * result + (a11 != +0.0f ? Float.floatToIntBits(a11) : 0); + temp = Double.doubleToLongBits(a12); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + a13.hashCode(); + result = 31 * result + a14.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/afterNameConflicts.java b/java/java-tests/testData/codeInsight/generateEquals/afterNameConflicts.java new file mode 100644 index 000000000000..712aaa2a544a --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/afterNameConflicts.java @@ -0,0 +1,73 @@ +import java.util.Arrays; + +class A { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + double temp; + + Object result; + String a14; + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final A a = (A) o; + + if (a10 != a.a10) return false; + if (Float.compare(a.a11, a11) != 0) return false; + if (a7 != a.a7) return false; + if (a8 != a.a8) return false; + if (a9 != a.a9) return false; + if (Double.compare(a.temp, temp) != 0) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(a1, a.a1)) return false; + if (!a14.equals(a.a14)) return false; + if (!Arrays.deepEquals(a2, a.a2)) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(a3, a.a3)) return false; + if (!Arrays.deepEquals(a4, a.a4)) return false; + if (!Arrays.equals(a5, a.a5)) return false; + if (!Arrays.deepEquals(a6, a.a6)) return false; + if (!result.equals(a.result)) return false; + + return true; + } + + @Override + public int hashCode() { + int result1; + long temp1; + result1 = Arrays.hashCode(a1); + result1 = 31 * result1 + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a2); + result1 = 31 * result1 + Arrays.hashCode(a3); + result1 = 31 * result1 + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a4); + result1 = 31 * result1 + Arrays.hashCode(a5); + result1 = 31 * result1 + // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode + Arrays.hashCode(a6); + result1 = 31 * result1 + (int) a7; + result1 = 31 * result1 + (int) a8; + result1 = 31 * result1 + a9; + result1 = 31 * result1 + (int) (a10 ^ (a10 >>> 32)); + result1 = 31 * result1 + (a11 != +0.0f ? Float.floatToIntBits(a11) : 0); + temp1 = Double.doubleToLongBits(temp); + result1 = 31 * result1 + (int) (temp1 ^ (temp1 >>> 32)); + result1 = 31 * result1 + result.hashCode(); + result1 = 31 * result1 + a14.hashCode(); + return result1; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypes.java b/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypes.java new file mode 100644 index 000000000000..6fe35fcafeb2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypes.java @@ -0,0 +1,21 @@ + +class A { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + double a12; + + Object a13; + String a14; + + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesAllNotNull.java b/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesAllNotNull.java new file mode 100644 index 000000000000..6fe35fcafeb2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesAllNotNull.java @@ -0,0 +1,21 @@ + +class A { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + double a12; + + Object a13; + String a14; + + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesNoDouble.java b/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesNoDouble.java new file mode 100644 index 000000000000..d2379f1fe788 --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesNoDouble.java @@ -0,0 +1,20 @@ + +class A { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + + Object a13; + String a14; + + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesSuperEqualsAndHashCode.java b/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesSuperEqualsAndHashCode.java new file mode 100644 index 000000000000..b123e793ba14 --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/beforeDifferentTypesSuperEqualsAndHashCode.java @@ -0,0 +1,31 @@ +class B { + @Override + public boolean equals(Object obj) { + return obj != null; + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} +class A extends B { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + double a12; + + Object a13; + String a14; + + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/generateEquals/beforeNameConflicts.java b/java/java-tests/testData/codeInsight/generateEquals/beforeNameConflicts.java new file mode 100644 index 000000000000..b6189c6647f2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/generateEquals/beforeNameConflicts.java @@ -0,0 +1,21 @@ + +class A { + Object[] a1; + Object[][] a2; + String[] a3; + String[][] a4; + int[] a5; + int[][] a6; + + byte a7; + short a8; + int a9; + long a10; + float a11; + double temp; + + Object result; + String a14; + + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/GenerateEquals15Test.java b/java/java-tests/testSrc/com/intellij/codeInsight/GenerateEquals15Test.java index 9026d776f077..992f237c435e 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/GenerateEquals15Test.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/GenerateEquals15Test.java @@ -1,5 +1,8 @@ package com.intellij.codeInsight; +import com.intellij.psi.PsiField; +import com.intellij.util.Function; + /** * @author dsl */ @@ -7,4 +10,31 @@ public class GenerateEquals15Test extends GenerateEqualsTestCase { public void testArraysFromJava15() throws Exception { doTest(new int[]{0, 1, 2}, new int[]{0, 1, 2}, new int[0], false); } + + public void testDifferentTypes() throws Exception { + doTest(Function.ID, Function.ID, new Function() { + @Override + public PsiField[] fun(PsiField[] fields) { + return new PsiField[0]; + } + }, true + ); + } + + public void testDifferentTypesAllNotNull() throws Exception { + doTest(Function.ID, Function.ID, Function.ID, true); + } + + public void testDifferentTypesSuperEqualsAndHashCode() throws Exception { + doTest(Function.ID, Function.ID, Function.ID, true); + } + + public void testDifferentTypesNoDouble() throws Exception { + doTest(Function.ID, Function.ID, Function.ID, true); + } + + public void testNameConflicts() throws Exception { + doTest(Function.ID, Function.ID, Function.ID, true); + } + } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/GenerateEqualsTestCase.java b/java/java-tests/testSrc/com/intellij/codeInsight/GenerateEqualsTestCase.java index 6a0ce2077051..db1c6ac768e6 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/GenerateEqualsTestCase.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/GenerateEqualsTestCase.java @@ -9,6 +9,7 @@ import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.LightCodeInsightTestCase; +import com.intellij.util.Function; import java.util.ArrayList; @@ -16,13 +17,38 @@ import java.util.ArrayList; * @author yole */ public abstract class GenerateEqualsTestCase extends LightCodeInsightTestCase { - protected void doTest(int[] equals, int[] hashCode, int[] nonNull, boolean insertOverride) throws Exception { + protected void doTest(final int[] equals, + final int[] hashCode, + final int[] nonNull, + boolean insertOverride) throws Exception { + doTest(new Function() { + @Override + public PsiField[] fun(PsiField[] fields) { + return getIndexed(fields, equals); + } + }, new Function() { + @Override + public PsiField[] fun(PsiField[] fields) { + return getIndexed(fields, hashCode); + } + }, new Function() { + @Override + public PsiField[] fun(PsiField[] fields) { + return getIndexed(fields, nonNull); + } + }, insertOverride); + } + + protected void doTest(Function eqFunction, + Function hFunction, + Function nnFunction, + boolean insertOverride) throws Exception { configureByFile("/codeInsight/generateEquals/before" + getTestName(false) + ".java"); - performTest(equals, hashCode, nonNull, insertOverride); + performTest(eqFunction, hFunction, nnFunction, insertOverride); checkResultByFile("/codeInsight/generateEquals/after" + getTestName(false) + ".java"); } - private static void performTest(int[] equals, int[] hashCode, int[] nonNull, boolean insertOverride) { + private static void performTest(Function equals, Function hashCode, Function nonNull, boolean insertOverride) { CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(getProject()).clone(); settings.GENERATE_FINAL_LOCALS = true; settings.INSERT_OVERRIDE_ANNOTATION = insertOverride; @@ -33,9 +59,7 @@ public abstract class GenerateEqualsTestCase extends LightCodeInsightTestCase { PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); if (aClass == null) return; PsiField[] fields = aClass.getFields(); - new GenerateEqualsHelper(getProject(), aClass, getIndexed(fields, equals), getIndexed(fields, hashCode), getIndexed(fields, nonNull), - false) - .invoke(); + new GenerateEqualsHelper(getProject(), aClass, equals.fun(fields), hashCode.fun(fields), nonNull.fun(fields), false).invoke(); FileDocumentManager.getInstance().saveAllDocuments(); } finally { diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettings.java b/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettings.java index 2f8d58820f09..7e83050c4593 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettings.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettings.java @@ -765,6 +765,18 @@ public class CodeStyleSettings extends CommonCodeStyleSettings implements Clonea return getIndentOptions(fileType).USE_TAB_CHARACTER; } + //used in generate equals/hashCode + @SuppressWarnings("unused") + public boolean isGenerateFinalLocals() { + return GENERATE_FINAL_LOCALS; + } + + //used in generate equals/hashCode + @SuppressWarnings("unused") + public boolean isGenerateFinalParameters() { + return GENERATE_FINAL_PARAMETERS; + } + public static class TypeToNameMap implements JDOMExternalizable { private final List myPatterns = new ArrayList(); private final List myNames = new ArrayList(); diff --git a/plugins/generate-tostring/src/org/jetbrains/java/generate/GenerationUtil.java b/plugins/generate-tostring/src/org/jetbrains/java/generate/GenerationUtil.java index 362709a503c0..4b5f3ab0c2ae 100644 --- a/plugins/generate-tostring/src/org/jetbrains/java/generate/GenerationUtil.java +++ b/plugins/generate-tostring/src/org/jetbrains/java/generate/GenerationUtil.java @@ -21,11 +21,11 @@ import com.intellij.codeInsight.generation.PsiMethodMember; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiMember; -import com.intellij.psi.PsiMethod; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.codeStyle.VariableKind; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.jetbrains.annotations.Nullable; @@ -119,14 +119,16 @@ public class GenerationUtil { /** * Generates the code using Velocity. - *

+ *

* This is used to create the toString method body and it's javadoc. * - * @param selectedMembers the selected members as both {@link com.intellij.psi.PsiField} and {@link com.intellij.psi.PsiMethod}. - * @param params additional parameters stored with key/value in the map. - * @param templateMacro the velocity macro template - * @return code (usually javacode). Returns null if templateMacro is null. - * @throws org.jetbrains.java.generate.exception.GenerateCodeException is thrown when there is an error generating the javacode. + * @param clazz + * @param selectedMembers the selected members as both {@link PsiField} and {@link PsiMethod}. + * @param params additional parameters stored with key/value in the map. + * @param templateMacro the velocity macro template + * @param sortElements + * @param useFullyQualifiedName @return code (usually javacode). Returns null if templateMacro is null. + * @throws GenerateCodeException is thrown when there is an error generating the javacode. */ public static String velocityGenerateCode(PsiClass clazz, Collection selectedMembers, @@ -135,6 +137,29 @@ public class GenerationUtil { int sortElements, boolean useFullyQualifiedName) throws GenerateCodeException { + return velocityGenerateCode(clazz, selectedMembers, Collections.emptyList(), params, Collections.emptyMap(), templateMacro, sortElements, useFullyQualifiedName); + } + + /** + * Generates the code using Velocity. + *

+ * This is used to create the toString method body and it's javadoc. + * + * @param selectedMembers the selected members as both {@link PsiField} and {@link PsiMethod}. + * @param params additional parameters stored with key/value in the map. + * @param templateMacro the velocity macro template + * @return code (usually javacode). Returns null if templateMacro is null. + * @throws GenerateCodeException is thrown when there is an error generating the javacode. + */ + public static String velocityGenerateCode(PsiClass clazz, + Collection selectedMembers, + Collection selectedNotNullMembers, + Map params, + Map contextMap, + String templateMacro, + int sortElements, + boolean useFullyQualifiedName) + throws GenerateCodeException { if (templateMacro == null) { return null; } @@ -155,7 +180,7 @@ public class GenerationUtil { // element information (both fields and methods) logger.debug("Velocity Context - adding members (fields and methods)"); - List elements = ElementUtils.getOnlyAsFieldAndMethodElements(selectedMembers); + List elements = ElementUtils.getOnlyAsFieldAndMethodElements(selectedMembers, selectedNotNullMembers); // sort elements if enabled and not using chooser dialog if (sortElements != 0) { Collections.sort(elements, new ElementComparator(sortElements)); @@ -171,6 +196,11 @@ public class GenerationUtil { vc.put("classname", useFullyQualifiedName ? ce.getQualifiedName() : ce.getName()); vc.put("FQClassname", ce.getQualifiedName()); vc.put("settings", CodeStyleSettingsManager.getSettings(clazz.getProject())); + vc.put("helper", GenerationHelper.class); + + for (String paramName : contextMap.keySet()) { + vc.put(paramName, contextMap.get(paramName)); + } if (logger.isDebugEnabled()) logger.debug("Velocity Macro:\n" + templateMacro); diff --git a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/AbstractElement.java b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/AbstractElement.java index ec931a1e404e..6958bdc6d779 100644 --- a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/AbstractElement.java +++ b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/AbstractElement.java @@ -53,8 +53,10 @@ public abstract class AbstractElement implements Element { protected boolean isModifierPackageLocal; protected boolean isModifierPrivate; protected boolean isModifierFinal; + private boolean isNotNull; + private boolean isNestedArray; - public String getName() { + public String getName() { return name; } @@ -62,6 +64,15 @@ public abstract class AbstractElement implements Element { return isArray; } + @Override + public boolean isNestedArray() { + return isNestedArray; + } + + public void setNestedArray(boolean isNestedArray) { + this.isNestedArray = isNestedArray; + } + public boolean isCollection() { return isCollection; } @@ -158,7 +169,16 @@ public abstract class AbstractElement implements Element { return isVoid; } - public void setVoid(boolean isVoid) { + @Override + public boolean isNotNull() { + return isNotNull; + } + + public void setNotNull(boolean isNotNull) { + this.isNotNull = isNotNull; + } + + public void setVoid(boolean isVoid) { this.isVoid = isVoid; } diff --git a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/Element.java b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/Element.java index bf23afa33e7d..5619729f8769 100644 --- a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/Element.java +++ b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/Element.java @@ -48,6 +48,8 @@ public interface Element { */ boolean isArray(); + boolean isNestedArray(); + /** * Is this element a {@link java.util.Collection} type (isAssignableFrom java.util.Collection) * @@ -153,6 +155,7 @@ public interface Element { boolean isDouble(); boolean isByte(); boolean isVoid(); + boolean isNotNull(); /** * Get's the elements type classname (etc. Object, String, List) diff --git a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/ElementFactory.java b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/ElementFactory.java index 2c219f1900fa..693e2278c270 100644 --- a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/ElementFactory.java +++ b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/ElementFactory.java @@ -177,7 +177,7 @@ public class ElementFactory { if (PsiType.BYTE.equals(type)) element.setByte(true); if (PsiType.CHAR.equals(type)) element.setChar(true); if (PsiType.SHORT.equals(type)) element.setShort(true); - + element.setNestedArray(PsiAdapter.isNestedArray(type)); // modifiers if (modifiers != null) { if (modifiers.hasModifierProperty(PsiModifier.STATIC)) element.setModifierStatic(true); diff --git a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/ElementUtils.java b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/ElementUtils.java index e0caa4ceee2b..a7ef483a6b5a 100644 --- a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/ElementUtils.java +++ b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/ElementUtils.java @@ -73,21 +73,27 @@ public class ElementUtils { /** * Gets the list of members to be put in the VelocityContext. * - * @param members a list of {@link com.intellij.psi.PsiMember} objects. + * @param members a list of {@link PsiMember} objects. + * @param selectedNotNullMembers a list of @NotNull objects * @return a filtered list of only the methods as a {@link FieldElement} or {@link MethodElement} objects. */ - public static List getOnlyAsFieldAndMethodElements(Collection members) { + public static List getOnlyAsFieldAndMethodElements(Collection members, + Collection selectedNotNullMembers) { List elementList = new ArrayList(); for (PsiMember member : members) { + AbstractElement element = null; if (member instanceof PsiField) { - PsiField field = (PsiField) member; - FieldElement fe = ElementFactory.newFieldElement(field); - elementList.add(fe); + element = ElementFactory.newFieldElement((PsiField) member); } else if (member instanceof PsiMethod) { - PsiMethod method = (PsiMethod) member; - MethodElement me = ElementFactory.newMethodElement(method); - elementList.add(me); + element = ElementFactory.newMethodElement((PsiMethod) member); + } + + if (element != null) { + if (selectedNotNullMembers.contains(member)) { + element.setNotNull(true); + } + elementList.add(element); } } return elementList; diff --git a/plugins/generate-tostring/src/org/jetbrains/java/generate/element/GenerationHelper.java b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/GenerationHelper.java new file mode 100644 index 000000000000..1b21e9c77de2 --- /dev/null +++ b/plugins/generate-tostring/src/org/jetbrains/java/generate/element/GenerationHelper.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.generate.element; + +import com.intellij.psi.codeStyle.CodeStyleSettings; + +import java.util.List; + +public class GenerationHelper { + + //used in generate equals/hashCode + @SuppressWarnings("unused") + public static String getUniqueLocalVarName(String base, List elements, CodeStyleSettings settings) { + base = settings.LOCAL_VARIABLE_NAME_PREFIX + base; + String id = base; + int index = 0; + while (true) { + if (index > 0) { + id = base + index; + } + index++; + boolean anyEqual = false; + for (Element equalsField : elements) { + if (id.equals(equalsField.getName())) { + anyEqual = true; + break; + } + } + if (!anyEqual) break; + } + + + return id; + } +}