2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.codeInsight
.generation
;
18 import com
.intellij
.codeInsight
.CodeInsightBundle
;
19 import com
.intellij
.openapi
.application
.ApplicationManager
;
20 import com
.intellij
.openapi
.diagnostic
.Logger
;
21 import com
.intellij
.openapi
.module
.Module
;
22 import com
.intellij
.openapi
.module
.ModuleUtil
;
23 import com
.intellij
.openapi
.project
.Project
;
24 import com
.intellij
.psi
.*;
25 import com
.intellij
.psi
.codeStyle
.*;
26 import com
.intellij
.psi
.search
.GlobalSearchScope
;
27 import com
.intellij
.psi
.util
.MethodSignature
;
28 import com
.intellij
.psi
.util
.MethodSignatureUtil
;
29 import com
.intellij
.psi
.util
.PsiUtil
;
30 import com
.intellij
.util
.IncorrectOperationException
;
31 import com
.intellij
.util
.containers
.HashMap
;
32 import org
.jetbrains
.annotations
.NonNls
;
34 import java
.text
.MessageFormat
;
40 public class GenerateEqualsHelper
implements Runnable
{
41 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.generation.GenerateEqualsHelper");
42 private final PsiClass myClass
;
43 private final PsiField
[] myEqualsFields
;
44 private final PsiField
[] myHashCodeFields
;
45 private final HashSet
<PsiField
> myNonNullSet
;
46 private final PsiElementFactory myFactory
;
47 private String myParameterName
;
49 @NonNls private static final String BASE_OBJECT_PARAMETER_NAME
= "object";
50 @NonNls private static final String BASE_OBJECT_LOCAL_NAME
= "that";
51 @NonNls private static final String RESULT_VARIABLE
= "result";
52 @NonNls private static final String TEMP_VARIABLE
= "temp";
54 private String myClassInstanceName
;
56 @NonNls private static final HashMap
<String
, MessageFormat
> PRIMITIVE_HASHCODE_FORMAT
= new HashMap
<String
, MessageFormat
>();
57 private final boolean mySuperHasHashCode
;
58 private final CodeStyleManager myCodeStyleManager
;
59 private final JavaCodeStyleManager myJavaCodeStyleManager
;
60 private final Project myProject
;
61 private final boolean myCheckParameterWithInstanceof
;
63 public GenerateEqualsHelper(Project project
,
65 PsiField
[] equalsFields
,
66 PsiField
[] hashCodeFields
,
67 PsiField
[] nonNullFields
,
68 boolean useInstanceofToCheckParameterType
) {
70 myEqualsFields
= equalsFields
;
71 myHashCodeFields
= hashCodeFields
;
73 myCheckParameterWithInstanceof
= useInstanceofToCheckParameterType
;
75 myNonNullSet
= new HashSet
<PsiField
>();
76 myNonNullSet
.addAll(Arrays
.asList(nonNullFields
));
77 final PsiManager manager
= PsiManager
.getInstance(project
);
79 myFactory
= JavaPsiFacade
.getInstance(manager
.getProject()).getElementFactory();
81 mySuperHasHashCode
= superMethodExists(getHashCodeSignature());
82 myCodeStyleManager
= CodeStyleManager
.getInstance(manager
.getProject());
83 myJavaCodeStyleManager
= JavaCodeStyleManager
.getInstance(manager
.getProject());
86 private static String
getUniqueLocalVarName(String base
, PsiField
[] fields
) {
94 boolean anyEqual
= false;
95 for (PsiField equalsField
: fields
) {
96 if (id
.equals(equalsField
.getName())) {
101 if (!anyEqual
) break;
110 final Collection
<PsiMethod
> members
= generateMembers();
111 for (PsiElement member
: members
) {
115 catch (IncorrectOperationException e
) {
120 public Collection
<PsiMethod
> generateMembers() throws IncorrectOperationException
{
121 PsiMethod equals
= null;
122 if (myEqualsFields
!= null && findMethod(myClass
, getEqualsSignature(myProject
, myClass
.getResolveScope())) == null) {
123 equals
= createEquals();
126 PsiMethod hashCode
= null;
127 if (myHashCodeFields
!= null && findMethod(myClass
, getHashCodeSignature()) == null) {
128 if (myHashCodeFields
.length
> 0) {
129 hashCode
= createHashCode();
132 if (!mySuperHasHashCode
) {
133 @NonNls String text
= "";
134 CodeStyleSettings styleSettings
= CodeStyleSettingsManager
.getSettings(myProject
);
135 if (styleSettings
.INSERT_OVERRIDE_ANNOTATION
&& PsiUtil
.isLanguageLevel5OrHigher(myClass
)) {
136 text
+= "@Override\n";
139 text
+= "public int hashCode() {\nreturn 0;\n}";
140 final PsiMethod trivialHashCode
= myFactory
.createMethodFromText(text
, null);
141 hashCode
= (PsiMethod
)myCodeStyleManager
.reformat(trivialHashCode
);
145 if (hashCode
!= null && equals
!= null) {
146 return Arrays
.asList(equals
, hashCode
);
148 else if (equals
!= null) {
149 return Collections
.singletonList(equals
);
151 else if (hashCode
!= null) {
152 return Collections
.singletonList(hashCode
);
155 return Collections
.emptyList();
160 private PsiMethod
createEquals() throws IncorrectOperationException
{
161 JavaCodeStyleManager codeStyleManager
= myJavaCodeStyleManager
;
162 final PsiType objectType
= PsiType
.getJavaLangObject(myClass
.getManager(), myClass
.getResolveScope());
163 String
[] nameSuggestions
= codeStyleManager
.suggestVariableName(VariableKind
.PARAMETER
, null, null, objectType
).names
;
164 final String objectBaseName
= nameSuggestions
.length
> 0 ? nameSuggestions
[0] : BASE_OBJECT_PARAMETER_NAME
;
165 myParameterName
= getUniqueLocalVarName(objectBaseName
, myEqualsFields
);
166 final PsiType classType
= myFactory
.createType(myClass
);
167 nameSuggestions
= codeStyleManager
.suggestVariableName(VariableKind
.LOCAL_VARIABLE
, null, null, classType
).names
;
168 String instanceBaseName
= nameSuggestions
.length
> 0 && nameSuggestions
[0].length() < 10 ? nameSuggestions
[0] : BASE_OBJECT_LOCAL_NAME
;
169 myClassInstanceName
= getUniqueLocalVarName(instanceBaseName
, myEqualsFields
);
171 @NonNls StringBuffer buffer
= new StringBuffer();
172 CodeStyleSettings styleSettings
= CodeStyleSettingsManager
.getSettings(myProject
);
173 if (styleSettings
.INSERT_OVERRIDE_ANNOTATION
&& PsiUtil
.isLanguageLevel5OrHigher(myClass
)) {
174 buffer
.append("@Override\n");
176 buffer
.append("public boolean equals(Object ").append(myParameterName
).append(") {\n");
177 addEqualsPrologue(buffer
);
178 if (myEqualsFields
.length
> 0) {
179 addClassInstance(buffer
);
181 ArrayList
<PsiField
> equalsFields
= new ArrayList
<PsiField
>();
182 equalsFields
.addAll(Arrays
.asList(myEqualsFields
));
183 Collections
.sort(equalsFields
, EqualsFieldsComparator
.INSTANCE
);
185 for (PsiField field
: equalsFields
) {
186 if (!field
.hasModifierProperty(PsiModifier
.STATIC
)) {
187 final PsiType type
= field
.getType();
188 if (type
instanceof PsiArrayType
) {
189 addArrayEquals(buffer
, field
);
191 else if (type
instanceof PsiPrimitiveType
) {
192 if (PsiType
.DOUBLE
.equals(type
) || PsiType
.FLOAT
.equals(type
)) {
193 addDoubleFieldComparison(buffer
, field
);
196 addPrimitiveFieldComparison(buffer
, field
);
200 if (type
instanceof PsiClassType
) {
201 final PsiClass aClass
= ((PsiClassType
)type
).resolve();
202 if (aClass
!= null && aClass
.isEnum()) {
203 addPrimitiveFieldComparison(buffer
, field
);
207 addFieldComparison(buffer
, field
);
212 buffer
.append("\nreturn true;\n}");
213 PsiMethod result
= myFactory
.createMethodFromText(buffer
.toString(), null);
214 final PsiParameter parameter
= result
.getParameterList().getParameters()[0];
215 PsiUtil
.setModifierProperty(parameter
, PsiModifier
.FINAL
, styleSettings
.GENERATE_FINAL_PARAMETERS
);
217 PsiMethod method
= (PsiMethod
)myCodeStyleManager
.reformat(result
);
218 method
= (PsiMethod
)myJavaCodeStyleManager
.shortenClassReferences(method
);
222 private void addDoubleFieldComparison(final StringBuffer buffer
, final PsiField field
) {
223 @NonNls final String type
= PsiType
.DOUBLE
.equals(field
.getType()) ?
"Double" : "Float";
224 final Object
[] parameters
= new Object
[]{type
, myClassInstanceName
, field
.getName()};
225 DOUBLE_FIELD_COMPARER_MF
.format(parameters
, buffer
, null);
228 @NonNls private static final MessageFormat ARRAY_COMPARER_MF
=
229 new MessageFormat("if(!java.util.Arrays.equals({1}, {0}.{1})) return false;\n");
230 @NonNls private static final MessageFormat FIELD_COMPARER_MF
=
231 new MessageFormat("if({1}!=null ? !{1}.equals({0}.{1}) : {0}.{1}!= null)return false;\n");
232 @NonNls private static final MessageFormat NON_NULL_FIELD_COMPARER_MF
= new MessageFormat("if(!{1}.equals({0}.{1}))return false;\n");
233 @NonNls private static final MessageFormat PRIMITIVE_FIELD_COMPARER_MF
= new MessageFormat("if({1}!={0}.{1})return false;\n");
234 @NonNls private static final MessageFormat DOUBLE_FIELD_COMPARER_MF
=
235 new MessageFormat("if({0}.compare({1}.{2}, {2}) != 0)return false;\n");
237 private void addArrayEquals(StringBuffer buffer
, PsiField field
) {
238 final PsiType fieldType
= field
.getType();
239 if (isNestedArray(fieldType
)) {
241 buffer
.append(CodeInsightBundle
.message("generate.equals.compare.nested.arrays.comment", field
.getName()));
245 if (isArrayOfObjects(fieldType
)) {
247 buffer
.append(CodeInsightBundle
.message("generate.equals.compare.arrays.comment"));
251 ARRAY_COMPARER_MF
.format(getComparerFormatParameters(field
), buffer
, null);
254 private Object
[] getComparerFormatParameters(PsiField field
) {
255 return new Object
[]{myClassInstanceName
, field
.getName()};
259 private void addFieldComparison(StringBuffer buffer
, PsiField field
) {
260 boolean canBeNull
= !myNonNullSet
.contains(field
);
262 FIELD_COMPARER_MF
.format(getComparerFormatParameters(field
), buffer
, null);
265 NON_NULL_FIELD_COMPARER_MF
.format(getComparerFormatParameters(field
), buffer
, null);
269 private void addPrimitiveFieldComparison(StringBuffer buffer
, PsiField field
) {
270 PRIMITIVE_FIELD_COMPARER_MF
.format(getComparerFormatParameters(field
), buffer
, null);
273 @SuppressWarnings("HardCodedStringLiteral")
274 private void addInstanceOfToText(@NonNls StringBuffer buffer
, String returnValue
) {
275 if (myCheckParameterWithInstanceof
) {
276 buffer
.append("if(!(").append(myParameterName
).append(" instanceof ").append(myClass
.getName())
277 .append(")) " + "return ").append(returnValue
).append(";\n");
280 buffer
.append("if(").append(myParameterName
).append("== null || getClass() != ").append(myParameterName
)
281 .append(".getClass()) " + "return ").append(returnValue
).append(";\n");
285 private void addEqualsPrologue(@NonNls StringBuffer buffer
) {
286 buffer
.append("if(this==");
287 buffer
.append(myParameterName
);
288 buffer
.append(") return true;\n");
289 if (!superMethodExists(getEqualsSignature(myProject
, myClass
.getResolveScope()))) {
290 addInstanceOfToText(buffer
, Boolean
.toString(false));
293 addInstanceOfToText(buffer
, Boolean
.toString(false));
294 buffer
.append("if(!super.equals(");
295 buffer
.append(myParameterName
);
296 buffer
.append(")) return false;\n");
300 private void addClassInstance(@NonNls StringBuffer buffer
) {
303 CodeStyleSettings settings
= CodeStyleSettingsManager
.getSettings(myCodeStyleManager
.getProject());
304 if (settings
.GENERATE_FINAL_LOCALS
) {
305 buffer
.append("final ");
308 buffer
.append(myClass
.getName());
309 buffer
.append(" ").append(myClassInstanceName
).append(" = (");
310 buffer
.append(myClass
.getName());
312 buffer
.append(myParameterName
);
313 buffer
.append(";\n\n");
317 private boolean superMethodExists(MethodSignature methodSignature
) {
318 LOG
.assertTrue(myClass
.isValid());
319 PsiMethod superEquals
= MethodSignatureUtil
.findMethodBySignature(myClass
, methodSignature
, true);
320 if (superEquals
== null) return true;
321 if (superEquals
.hasModifierProperty(PsiModifier
.ABSTRACT
)) return false;
322 return !CommonClassNames
.JAVA_LANG_OBJECT
.equals(superEquals
.getContainingClass().getQualifiedName());
325 private PsiMethod
createHashCode() throws IncorrectOperationException
{
326 @NonNls StringBuilder buffer
= new StringBuilder();
328 CodeStyleSettings styleSettings
= CodeStyleSettingsManager
.getSettings(myProject
);
329 if (styleSettings
.INSERT_OVERRIDE_ANNOTATION
&& PsiUtil
.isLanguageLevel5OrHigher(myClass
)) {
330 buffer
.append("@Override\n");
332 buffer
.append("public int hashCode() {\n");
333 if (!mySuperHasHashCode
&& myHashCodeFields
.length
== 1) {
334 PsiField field
= myHashCodeFields
[0];
335 final String tempName
= addTempForOneField(field
, buffer
);
336 buffer
.append("return ");
337 if (field
.getType() instanceof PsiPrimitiveType
) {
338 addPrimitiveFieldHashCode(buffer
, field
, tempName
);
341 addFieldHashCode(buffer
, field
, false);
343 buffer
.append(";\n}");
345 else if (myHashCodeFields
.length
> 0) {
346 CodeStyleSettings settings
= CodeStyleSettingsManager
.getSettings(myCodeStyleManager
.getProject());
347 final String resultName
= getUniqueLocalVarName(settings
.LOCAL_VARIABLE_NAME_PREFIX
+ RESULT_VARIABLE
, myHashCodeFields
);
349 buffer
.append("int ");
350 buffer
.append(resultName
);
352 boolean resultAssigned
= false;
353 boolean resultDeclarationCompleted
= false;
354 if (mySuperHasHashCode
) {
355 buffer
.append(" = ");
356 addSuperHashCode(buffer
);
357 buffer
.append(";\n");
358 resultAssigned
= true;
359 resultDeclarationCompleted
= true;
361 String tempName
= addTempDeclaration(buffer
, resultDeclarationCompleted
);
362 if (tempName
!= null) {
363 resultDeclarationCompleted
= true;
365 for (PsiField field
: myHashCodeFields
) {
366 addTempAssignment(field
, buffer
, tempName
);
367 if (resultDeclarationCompleted
) {
368 buffer
.append(resultName
);
371 buffer
.append(" = ");
372 if (resultAssigned
) {
373 buffer
.append("31*");
374 buffer
.append(resultName
);
375 buffer
.append(" + ");
377 if (field
.getType() instanceof PsiPrimitiveType
) {
378 addPrimitiveFieldHashCode(buffer
, field
, tempName
);
381 addFieldHashCode(buffer
, field
, resultAssigned
);
383 buffer
.append(";\n");
384 resultAssigned
= true;
385 resultDeclarationCompleted
= true;
387 buffer
.append("return ");
388 buffer
.append(resultName
);
389 buffer
.append(";\n}");
392 buffer
.append("return 0;\n}");
394 PsiMethod hashCode
= myFactory
.createMethodFromText(buffer
.toString(), null);
395 return (PsiMethod
)myCodeStyleManager
.reformat(hashCode
);
398 private static void addTempAssignment(PsiField field
, StringBuilder buffer
, String tempName
) {
399 if (PsiType
.DOUBLE
.equals(field
.getType())) {
400 buffer
.append(tempName
);
401 addTempForDoubleInitialization(field
, buffer
);
405 private static void addTempForDoubleInitialization(PsiField field
, @NonNls StringBuilder buffer
) {
406 buffer
.append(" = ");
407 buffer
.append(field
.getName());
408 buffer
.append(" != +0.0d ? Double.doubleToLongBits(");
409 buffer
.append(field
.getName());
410 buffer
.append(") : 0L;\n");
413 private String
addTempDeclaration(@NonNls StringBuilder buffer
, boolean resultDeclarationCompleted
) {
414 for (PsiField hashCodeField
: myHashCodeFields
) {
415 if (PsiType
.DOUBLE
.equals(hashCodeField
.getType())) {
416 final String name
= getUniqueLocalVarName(TEMP_VARIABLE
, myHashCodeFields
);
417 if (!resultDeclarationCompleted
) {
418 buffer
.append("\n;");
420 buffer
.append("long ");
422 buffer
.append(";\n");
429 @SuppressWarnings("HardCodedStringLiteral")
430 private String
addTempForOneField(PsiField field
, StringBuilder buffer
) {
431 if (PsiType
.DOUBLE
.equals(field
.getType())) {
432 final String name
= getUniqueLocalVarName(TEMP_VARIABLE
, myHashCodeFields
);
433 CodeStyleSettings settings
= CodeStyleSettingsManager
.getSettings(myCodeStyleManager
.getProject());
434 if (settings
.GENERATE_FINAL_LOCALS
) {
435 buffer
.append("final ");
437 buffer
.append("long ").append(name
);
438 addTempForDoubleInitialization(field
, buffer
);
446 private static void addPrimitiveFieldHashCode(StringBuilder buffer
, PsiField field
, String tempName
) {
447 MessageFormat format
= PRIMITIVE_HASHCODE_FORMAT
.get(field
.getType().getCanonicalText());
448 buffer
.append(format
.format(new Object
[]{field
.getName(), tempName
}));
451 private void addFieldHashCode(@NonNls StringBuilder buffer
, PsiField field
, boolean brace
) {
452 final String name
= field
.getName();
453 if (myNonNullSet
.contains(field
)) {
454 adjustHashCodeToArrays(buffer
, field
, name
);
461 buffer
.append(" != null ? ");
462 adjustHashCodeToArrays(buffer
, field
, name
);
463 buffer
.append(" : 0");
470 private static void adjustHashCodeToArrays(@NonNls StringBuilder buffer
, final PsiField field
, final String name
) {
471 if (field
.getType() instanceof PsiArrayType
&& hasArraysHashCode(field
)) {
472 buffer
.append("Arrays.hashCode(");
478 buffer
.append(".hashCode()");
482 private static boolean hasArraysHashCode(final PsiField field
) {
483 // the method was added in JDK 1.5 - check for actual method presence rather than language level
484 Module module
= ModuleUtil
.findModuleForPsiElement(field
);
485 if (module
== null) return false;
486 PsiClass arraysClass
= JavaPsiFacade
.getInstance(field
.getProject()).findClass("java.util.Arrays", module
.getModuleWithLibrariesScope());
487 if (arraysClass
== null) return false;
488 final PsiMethod
[] methods
= arraysClass
.findMethodsByName("hashCode", false);
489 return methods
.length
> 0;
492 @SuppressWarnings("HardCodedStringLiteral")
493 private void addSuperHashCode(StringBuilder buffer
) {
494 if (mySuperHasHashCode
) {
495 buffer
.append("super.hashCode()");
503 public void invoke() {
504 ApplicationManager
.getApplication().runWriteAction(this);
507 static PsiMethod
findMethod(PsiClass aClass
, MethodSignature signature
) {
508 return MethodSignatureUtil
.findMethodBySignature(aClass
, signature
, false);
511 static class EqualsFieldsComparator
implements Comparator
<PsiField
> {
512 public static final EqualsFieldsComparator INSTANCE
= new EqualsFieldsComparator();
514 public int compare(PsiField f1
, PsiField f2
) {
515 if (f1
.getType() instanceof PsiPrimitiveType
&& !(f2
.getType() instanceof PsiPrimitiveType
)) return -1;
516 if (!(f1
.getType() instanceof PsiPrimitiveType
) && f2
.getType() instanceof PsiPrimitiveType
) return 1;
517 final String name1
= f1
.getName();
518 final String name2
= f2
.getName();
519 assert name1
!= null && name2
!= null;
520 return name1
.compareTo(name2
);
525 initPrimitiveHashcodeFormats();
528 @SuppressWarnings("HardCodedStringLiteral")
529 private static void initPrimitiveHashcodeFormats() {
530 PRIMITIVE_HASHCODE_FORMAT
.put("byte", new MessageFormat("(int) {0}"));
531 PRIMITIVE_HASHCODE_FORMAT
.put("short", new MessageFormat("(int) {0}"));
532 PRIMITIVE_HASHCODE_FORMAT
.put("int", new MessageFormat("{0}"));
533 PRIMITIVE_HASHCODE_FORMAT
.put("long", new MessageFormat("(int) ({0} ^ ({0} >>> 32))"));
534 PRIMITIVE_HASHCODE_FORMAT
.put("boolean", new MessageFormat("({0} ? 1 : 0)"));
536 PRIMITIVE_HASHCODE_FORMAT
.put("float", new MessageFormat("({0} != +0.0f ? Float.floatToIntBits({0}) : 0)"));
537 PRIMITIVE_HASHCODE_FORMAT
.put("double", new MessageFormat("(int) ({1} ^ ({1} >>> 32))"));
539 PRIMITIVE_HASHCODE_FORMAT
.put("char", new MessageFormat("(int) {0}"));
540 PRIMITIVE_HASHCODE_FORMAT
.put("void", new MessageFormat("0"));
541 PRIMITIVE_HASHCODE_FORMAT
.put("void", new MessageFormat("({0} ? 1 : 0)"));
544 public static boolean isNestedArray(PsiType aType
) {
545 if (!(aType
instanceof PsiArrayType
)) return false;
546 final PsiType componentType
= ((PsiArrayType
)aType
).getComponentType();
547 return componentType
instanceof PsiArrayType
;
550 public static boolean isArrayOfObjects(PsiType aType
) {
551 if (!(aType
instanceof PsiArrayType
)) return false;
552 final PsiType componentType
= ((PsiArrayType
)aType
).getComponentType();
553 final PsiClass psiClass
= PsiUtil
.resolveClassInType(componentType
);
554 if (psiClass
== null) return false;
555 final String qName
= psiClass
.getQualifiedName();
556 return CommonClassNames
.JAVA_LANG_OBJECT
.equals(qName
);
559 public static MethodSignature
getHashCodeSignature() {
560 return MethodSignatureUtil
.createMethodSignature("hashCode", PsiType
.EMPTY_ARRAY
, PsiTypeParameter
.EMPTY_ARRAY
, PsiSubstitutor
.EMPTY
);
563 public static MethodSignature
getEqualsSignature(Project project
, GlobalSearchScope scope
) {
564 final PsiClassType javaLangObject
= PsiType
.getJavaLangObject(PsiManager
.getInstance(project
), scope
);
565 return MethodSignatureUtil
566 .createMethodSignature("equals", new PsiType
[]{javaLangObject
}, PsiTypeParameter
.EMPTY_ARRAY
, PsiSubstitutor
.EMPTY
);