update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / generation / GenerateEqualsHelper.java
blob90ef1b1ff0dfb0bc0cb456e64e4ef2ff3e7451c5
1 /*
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;
35 import java.util.*;
37 /**
38 * @author dsl
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,
64 PsiClass aClass,
65 PsiField[] equalsFields,
66 PsiField[] hashCodeFields,
67 PsiField[] nonNullFields,
68 boolean useInstanceofToCheckParameterType) {
69 myClass = aClass;
70 myEqualsFields = equalsFields;
71 myHashCodeFields = hashCodeFields;
72 myProject = project;
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) {
87 String id = base;
88 int index = 0;
89 while (true) {
90 if (index > 0) {
91 id = base + index;
93 index++;
94 boolean anyEqual = false;
95 for (PsiField equalsField : fields) {
96 if (id.equals(equalsField.getName())) {
97 anyEqual = true;
98 break;
101 if (!anyEqual) break;
105 return id;
108 public void run() {
109 try {
110 final Collection<PsiMethod> members = generateMembers();
111 for (PsiElement member : members) {
112 myClass.add(member);
115 catch (IncorrectOperationException e) {
116 LOG.error(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();
131 else {
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);
154 else {
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);
195 else {
196 addPrimitiveFieldComparison(buffer, field);
199 else {
200 if (type instanceof PsiClassType) {
201 final PsiClass aClass = ((PsiClassType)type).resolve();
202 if (aClass != null && aClass.isEnum()) {
203 addPrimitiveFieldComparison(buffer, field);
204 continue;
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);
219 return 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)) {
240 buffer.append(" ");
241 buffer.append(CodeInsightBundle.message("generate.equals.compare.nested.arrays.comment", field.getName()));
242 buffer.append("\n");
243 return;
245 if (isArrayOfObjects(fieldType)) {
246 buffer.append(" ");
247 buffer.append(CodeInsightBundle.message("generate.equals.compare.arrays.comment"));
248 buffer.append("\n");
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);
261 if (canBeNull) {
262 FIELD_COMPARER_MF.format(getComparerFormatParameters(field), buffer, null);
264 else {
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");
279 else {
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));
292 else {
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) {
301 buffer.append("\n");
302 // A a = (A) object;
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());
311 buffer.append(")");
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);
340 else {
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);
380 else {
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}");
391 else {
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 ");
421 buffer.append(name);
422 buffer.append(";\n");
423 return name;
426 return null;
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);
439 return name;
441 else {
442 return null;
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);
456 else {
457 if (brace) {
458 buffer.append("(");
460 buffer.append(name);
461 buffer.append(" != null ? ");
462 adjustHashCodeToArrays(buffer, field, name);
463 buffer.append(" : 0");
464 if (brace) {
465 buffer.append(")");
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(");
473 buffer.append(name);
474 buffer.append(")");
476 else {
477 buffer.append(name);
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()");
497 else {
498 buffer.append("0");
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);
524 static {
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);