generate equals/hashCode: use velocity templates

This commit is contained in:
Anna Kozlova
2015-01-09 21:02:03 +01:00
parent cf1ed92dcb
commit 0ee564a95e
26 changed files with 989 additions and 413 deletions
@@ -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;
}
}
@@ -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;
}
}
@@ -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<PsiField> 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<String, MessageFormat> PRIMITIVE_HASHCODE_FORMAT = new HashMap<String, MessageFormat>();
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<PsiField> equalsFields = new ArrayList<PsiField>();
ContainerUtil.addAll(equalsFields, myEqualsFields);
Collections.sort(equalsFields, EqualsFieldsComparator.INSTANCE);
ArrayList<PsiField> equalsFields = new ArrayList<PsiField>();
ContainerUtil.addAll(equalsFields, myEqualsFields);
Collections.sort(equalsFields, EqualsFieldsComparator.INSTANCE);
final HashMap<String, Object> contextMap = new HashMap<String, Object>();
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<String, String>(), 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<String, Object> contextMap = new HashMap<String, Object>();
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<String, String>(), 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();
@@ -1,2 +1,93 @@
//todo
//equals template to place here
#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
@@ -1,2 +1,130 @@
//todo
//hashCode template to place here
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
@@ -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<PsiClass,
final PsiField field = (PsiField)memberInfo.getMember();
if (!JavaVersionService.getInstance().isAtLeast(field, JavaSdkVersion.JDK_1_5)) {
final PsiType type = field.getType();
if (GenerateEqualsHelper.isNestedArray(type)) {
if (PsiAdapter.isNestedArray(type)) {
return CodeInsightBundle .message("generate.equals.warning.equals.for.nested.arrays.not.supported");
}
if (GenerateEqualsHelper.isArrayOfObjects(type)) {
@@ -285,7 +285,7 @@ public class GenerateEqualsWizard extends AbstractGenerateEqualsWizard<PsiClass,
if (!(member.getMember() instanceof PsiField)) return false;
final PsiField field = (PsiField)member.getMember();
final PsiType type = field.getType();
return JavaVersionService.getInstance().isAtLeast(field, JavaSdkVersion.JDK_1_5) || !GenerateEqualsHelper.isNestedArray(type);
return JavaVersionService.getInstance().isAtLeast(field, JavaSdkVersion.JDK_1_5) || !PsiAdapter.isNestedArray(type);
}
@Override
@@ -294,7 +294,7 @@ public class GenerateEqualsWizard extends AbstractGenerateEqualsWizard<PsiClass,
final PsiField field = (PsiField)member.getMember();
final PsiType type = field.getType();
if (!JavaVersionService.getInstance().isAtLeast(field, JavaSdkVersion.JDK_1_5)) {
if (GenerateEqualsHelper.isNestedArray(type)) return ERROR;
if (PsiAdapter.isNestedArray(type)) return ERROR;
if (GenerateEqualsHelper.isArrayOfObjects(type)) return WARNING;
}
return OK;
@@ -21,7 +21,7 @@ class Test {
public int hashCode() {
int result = myOs != null ? Arrays.hashCode(myOs) : 0;
result = 31 * result + (myIIs != null ? // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode
result = 31 * result + (myIIs != null ? // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode
Arrays.hashCode(myIIs) : 0);
result = 31 * result + (myIs != null ? Arrays.hashCode(myIs) : 0);
return result;
@@ -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 != null ? !a13.equals(a.a13) : a.a13 != null) return false;
if (a14 != null ? !a14.equals(a.a14) : a.a14 != null) 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 = a1 != null ? Arrays.hashCode(a1) : 0;
result = 31 * result + (a2 != null ? // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode
Arrays.hashCode(a2) : 0);
result = 31 * result + (a3 != null ? Arrays.hashCode(a3) : 0);
result = 31 * result + (a4 != null ? // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode
Arrays.hashCode(a4) : 0);
result = 31 * result + (a5 != null ? Arrays.hashCode(a5) : 0);
result = 31 * result + (a6 != null ? // Probably incorrect - hashCode for high dimension arrays with Arrays.hashCode
Arrays.hashCode(a6) : 0);
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 != null ? a13.hashCode() : 0);
result = 31 * result + (a14 != null ? a14.hashCode() : 0);
return result;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
<caret>
}
@@ -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;
<caret>
}
@@ -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;
<caret>
}
@@ -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;
<caret>
}
@@ -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;
<caret>
}
@@ -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<PsiField[], PsiField[]>() {
@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);
}
}
@@ -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<PsiField[], PsiField[]>() {
@Override
public PsiField[] fun(PsiField[] fields) {
return getIndexed(fields, equals);
}
}, new Function<PsiField[], PsiField[]>() {
@Override
public PsiField[] fun(PsiField[] fields) {
return getIndexed(fields, hashCode);
}
}, new Function<PsiField[], PsiField[]>() {
@Override
public PsiField[] fun(PsiField[] fields) {
return getIndexed(fields, nonNull);
}
}, insertOverride);
}
protected void doTest(Function<PsiField[], PsiField[]> eqFunction,
Function<PsiField[], PsiField[]> hFunction,
Function<PsiField[], PsiField[]> 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<PsiField[], PsiField[]> equals, Function<PsiField[], PsiField[]> hashCode, Function<PsiField[], PsiField[]> 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 {
@@ -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<String> myPatterns = new ArrayList<String>();
private final List<String> myNames = new ArrayList<String>();
@@ -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.
* <p/>
* <p>
* This is used to create the <code>toString</code> 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<? extends PsiMember> selectedMembers,
@@ -135,6 +137,29 @@ public class GenerationUtil {
int sortElements,
boolean useFullyQualifiedName)
throws GenerateCodeException {
return velocityGenerateCode(clazz, selectedMembers, Collections.<PsiMember>emptyList(), params, Collections.<String, Object>emptyMap(), templateMacro, sortElements, useFullyQualifiedName);
}
/**
* Generates the code using Velocity.
* <p/>
* This is used to create the <code>toString</code> 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<? extends PsiMember> selectedMembers,
Collection<? extends PsiMember> selectedNotNullMembers,
Map<String, String> params,
Map<String, Object> 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<Element> elements = ElementUtils.getOnlyAsFieldAndMethodElements(selectedMembers);
List<Element> 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);
@@ -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;
}
@@ -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)
@@ -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);
@@ -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<Element> getOnlyAsFieldAndMethodElements(Collection<? extends PsiMember> members) {
public static List<Element> getOnlyAsFieldAndMethodElements(Collection<? extends PsiMember> members,
Collection<? extends PsiMember> selectedNotNullMembers) {
List<Element> elementList = new ArrayList<Element>();
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;
@@ -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<Element> 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;
}
}