Equals/hashCode generation ignores selected fields (IDEADEV-36678)
[fedora-idea.git] / codeInsight / impl / com / intellij / codeInsight / generation / ui / GenerateEqualsWizard.java
blobac84989a1b9a814015083543fb42af890be6b48f
1 package com.intellij.codeInsight.generation.ui;
3 import com.intellij.codeInsight.CodeInsightBundle;
4 import com.intellij.codeInsight.CodeInsightSettings;
5 import com.intellij.codeInsight.generation.GenerateEqualsHelper;
6 import com.intellij.ide.wizard.AbstractWizard;
7 import com.intellij.ide.wizard.StepAdapter;
8 import com.intellij.openapi.diagnostic.Logger;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.openapi.ui.VerticalFlowLayout;
11 import com.intellij.psi.*;
12 import com.intellij.refactoring.ui.MemberSelectionPanel;
13 import com.intellij.refactoring.util.classMembers.MemberInfo;
14 import com.intellij.refactoring.util.classMembers.MemberInfoChange;
15 import com.intellij.refactoring.util.classMembers.MemberInfoModel;
16 import com.intellij.refactoring.util.classMembers.MemberInfoTooltipManager;
17 import com.intellij.ui.NonFocusableCheckBox;
18 import com.intellij.util.containers.HashMap;
19 import org.jetbrains.annotations.NotNull;
20 import org.jetbrains.annotations.Nullable;
22 import javax.swing.*;
23 import javax.swing.event.TableModelEvent;
24 import javax.swing.event.TableModelListener;
25 import java.awt.*;
26 import java.awt.event.ActionEvent;
27 import java.awt.event.ActionListener;
28 import java.util.ArrayList;
29 import java.util.Map;
31 /**
32 * @author dsl
34 public class GenerateEqualsWizard extends AbstractWizard {
35 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.ui.GenerateEqualsWizard");
36 private final PsiClass myClass;
38 private final MemberSelectionPanel myEqualsPanel;
39 private final MemberSelectionPanel myHashCodePanel;
40 private final HashMap myFieldsToHashCode;
41 private final MemberSelectionPanel myNonNullPanel;
42 private final HashMap<PsiElement, MemberInfo> myFieldsToNonNull;
44 private final int myTestBoxedStep;
45 private final int myEqualsStepCode;
46 private final int myHashcodeStepCode;
48 private final MemberInfo[] myClassFields;
49 private static final MyMemberInfoFilter MEMBER_INFO_FILTER = new MyMemberInfoFilter();
52 public GenerateEqualsWizard(Project project, PsiClass aClass, boolean needEquals, boolean needHashCode) {
53 super(CodeInsightBundle.message("generate.equals.hashcode.wizard.title"), project);
54 LOG.assertTrue(needEquals || needHashCode);
55 myClass = aClass;
57 myClassFields = MemberInfo.extractClassMembers(myClass, MEMBER_INFO_FILTER, false);
58 for (MemberInfo myClassField : myClassFields) {
59 myClassField.setChecked(true);
61 int testBoxedStep = 0;
62 if (needEquals) {
63 myEqualsPanel =
64 new MemberSelectionPanel(CodeInsightBundle.message("generate.equals.hashcode.equals.fields.chooser.title"), myClassFields, null);
65 myEqualsPanel.getTable().setMemberInfoModel(new EqualsMemberInfoModel());
66 testBoxedStep+=2;
68 else {
69 myEqualsPanel = null;
71 if (needHashCode) {
72 final MemberInfo[] hashCodeMemberInfos;
73 if (needEquals) {
74 myFieldsToHashCode = createFieldToMemberInfoMap(true);
75 hashCodeMemberInfos = new MemberInfo[0];
77 else {
78 hashCodeMemberInfos = myClassFields;
79 myFieldsToHashCode = null;
81 myHashCodePanel = new MemberSelectionPanel(CodeInsightBundle.message("generate.equals.hashcode.hashcode.fields.chooser.title"),
82 hashCodeMemberInfos, null);
83 myHashCodePanel.getTable().setMemberInfoModel(new HashCodeMemberInfoModel());
84 if (needEquals) {
85 updateHashCodeMemberInfos(myClassFields);
87 testBoxedStep++;
89 else {
90 myHashCodePanel = null;
91 myFieldsToHashCode = null;
93 myTestBoxedStep=testBoxedStep;
94 myNonNullPanel = new MemberSelectionPanel(CodeInsightBundle.message("generate.equals.hashcode.non.null.fields.chooser.title"),
95 new MemberInfo[0], null);
96 myFieldsToNonNull = createFieldToMemberInfoMap(false);
97 for (final Map.Entry<PsiElement, MemberInfo> entry : myFieldsToNonNull.entrySet()) {
98 entry.getValue().setChecked(((PsiField)entry.getKey()).getModifierList().findAnnotation(NotNull.class.getName()) != null);
101 final MyTableModelListener listener = new MyTableModelListener();
102 if (myEqualsPanel != null) {
103 myEqualsPanel.getTable().getModel().addTableModelListener(listener);
104 addStep(new InstanceofOptionStep());
105 addStep(new MyStep(myEqualsPanel));
106 myEqualsStepCode = 1;
108 else {
109 myEqualsStepCode = -1;
112 if (myHashCodePanel != null) {
113 myHashCodePanel.getTable().getModel().addTableModelListener(listener);
114 addStep(new MyStep(myHashCodePanel));
115 myHashcodeStepCode = myEqualsStepCode > 0 ? myEqualsStepCode + 1 : 1;
117 else {
118 myHashcodeStepCode = -1;
121 addStep(new MyStep(myNonNullPanel));
123 init();
124 updateStatus();
127 public PsiField[] getEqualsFields() {
128 if (myEqualsPanel != null) {
129 return memberInfosToFields(myEqualsPanel.getTable().getSelectedMemberInfos());
131 else {
132 return null;
136 public PsiField[] getHashCodeFields() {
137 if (myHashCodePanel != null) {
138 return memberInfosToFields(myHashCodePanel.getTable().getSelectedMemberInfos());
140 else {
141 return null;
145 public PsiField[] getNonNullFields() {
146 return memberInfosToFields(myNonNullPanel.getTable().getSelectedMemberInfos());
149 private static PsiField[] memberInfosToFields(MemberInfo[] infos) {
150 ArrayList<PsiField> list = new ArrayList<PsiField>();
151 for (MemberInfo info : infos) {
152 list.add((PsiField)info.getMember());
154 return list.toArray(new PsiField[list.size()]);
157 protected void doNextAction() {
158 if (getCurrentStep() == myEqualsStepCode && myEqualsPanel != null) {
159 equalsFieldsSelected();
161 else if (getCurrentStep() == myHashcodeStepCode && myHashCodePanel != null) {
162 MemberInfo[] selectedMemberInfos = myHashCodePanel.getTable().getSelectedMemberInfos();
163 updateNonNullMemberInfos(selectedMemberInfos);
166 super.doNextAction();
167 updateStatus();
170 protected void updateStep() {
171 super.updateStep();
172 final Component stepComponent = getCurrentStepComponent();
173 if (stepComponent instanceof MemberSelectionPanel) {
174 ((MemberSelectionPanel)stepComponent).getTable().requestFocus();
178 protected String getHelpID() {
179 return "editing.altInsert.equals";
182 private void equalsFieldsSelected() {
183 MemberInfo[] selectedMemberInfos = myEqualsPanel.getTable().getSelectedMemberInfos();
184 updateHashCodeMemberInfos(selectedMemberInfos);
185 updateNonNullMemberInfos(selectedMemberInfos);
188 @Override
189 protected void doOKAction() {
190 if (myEqualsPanel != null) {
191 equalsFieldsSelected();
193 super.doOKAction();
196 private HashMap<PsiElement, MemberInfo> createFieldToMemberInfoMap(boolean checkedByDefault) {
197 MemberInfo[] memberInfos = MemberInfo.extractClassMembers(myClass, MEMBER_INFO_FILTER, false);
198 final HashMap<PsiElement, MemberInfo> result = new HashMap<PsiElement, MemberInfo>();
199 for (MemberInfo memberInfo : memberInfos) {
200 memberInfo.setChecked(checkedByDefault);
201 result.put(memberInfo.getMember(), memberInfo);
203 return result;
206 private void updateHashCodeMemberInfos(MemberInfo[] equalsMemberInfos) {
207 if (myHashCodePanel == null) return;
208 MemberInfo[] hashCodeFields = new MemberInfo[equalsMemberInfos.length];
210 for (int i = 0; i < equalsMemberInfos.length; i++) {
211 hashCodeFields[i] = (MemberInfo)myFieldsToHashCode.get(equalsMemberInfos[i].getMember());
213 myHashCodePanel.getTable().setMemberInfos(hashCodeFields);
216 private void updateNonNullMemberInfos(MemberInfo[] equalsMemberInfos) {
217 final ArrayList<MemberInfo> list = new ArrayList<MemberInfo>();
219 for (MemberInfo equalsMemberInfo : equalsMemberInfos) {
220 PsiField field = (PsiField)equalsMemberInfo.getMember();
221 if (!(field.getType() instanceof PsiPrimitiveType)) {
222 list.add(myFieldsToNonNull.get(equalsMemberInfo.getMember()));
225 myNonNullPanel.getTable().setMemberInfos(list.toArray(new MemberInfo[list.size()]));
228 private void updateStatus() {
229 boolean finishEnabled = true;
230 boolean nextEnabled = true;
231 if (myEqualsPanel != null & getCurrentStep() < myEqualsStepCode) {
232 finishEnabled = false;
235 if (getCurrentStep() == myTestBoxedStep - 1) {
236 boolean anyNonBoxed = false;
237 for (MemberInfo classField : myClassFields) {
238 if (classField.isChecked()) {
239 PsiField field = (PsiField)classField.getMember();
240 if (!(field.getType() instanceof PsiPrimitiveType)) {
241 anyNonBoxed = true;
242 break;
246 nextEnabled = anyNonBoxed;
249 if (getCurrentStep() == myEqualsStepCode) {
250 boolean anyChecked = false;
251 for (MemberInfo classField : myClassFields) {
252 if (classField.isChecked()) {
253 anyChecked = true;
254 break;
257 finishEnabled &= anyChecked;
258 nextEnabled &= anyChecked;
261 if (getCurrentStep() == myTestBoxedStep) {
262 finishEnabled = true;
263 nextEnabled = false;
266 getFinishButton().setEnabled(finishEnabled);
267 getNextButton().setEnabled(nextEnabled);
269 if (finishEnabled) {
270 getRootPane().setDefaultButton(getFinishButton());
272 else if (nextEnabled) {
273 getRootPane().setDefaultButton(getNextButton());
277 public JComponent getPreferredFocusedComponent() {
278 final Component stepComponent = getCurrentStepComponent();
279 if (stepComponent instanceof MemberSelectionPanel) {
280 return ((MemberSelectionPanel)stepComponent).getTable();
282 else {
283 return null;
287 private class MyTableModelListener implements TableModelListener {
288 public void tableChanged(TableModelEvent e) {
289 updateStatus();
293 private static class InstanceofOptionStep extends StepAdapter {
294 private final JComponent myPanel;
296 private InstanceofOptionStep() {
297 final JCheckBox checkbox = new NonFocusableCheckBox(CodeInsightBundle.message("generate.equals.hashcode.accept.sublcasses"));
298 checkbox.setSelected(CodeInsightSettings.getInstance().USE_INSTANCEOF_ON_EQUALS_PARAMETER);
299 checkbox.addActionListener(new ActionListener() {
300 public void actionPerformed(final ActionEvent e) {
301 CodeInsightSettings.getInstance().USE_INSTANCEOF_ON_EQUALS_PARAMETER = checkbox.isSelected();
305 myPanel = new JPanel(new VerticalFlowLayout());
306 myPanel.add(checkbox);
307 myPanel.add(new JLabel(CodeInsightBundle.message("generate.equals.hashcode.accept.sublcasses.explanation")));
310 public JComponent getComponent() {
311 return myPanel;
314 @Nullable
315 public Icon getIcon() {
316 return null;
320 private static class MyStep extends StepAdapter {
321 final MemberSelectionPanel myPanel;
323 public MyStep(MemberSelectionPanel panel) {
324 myPanel = panel;
327 public Icon getIcon() {
328 return null;
331 public JComponent getComponent() {
332 return myPanel;
337 private static class MyMemberInfoFilter implements MemberInfo.Filter {
338 public boolean includeMember(PsiMember element) {
339 return element instanceof PsiField && !element.hasModifierProperty(PsiModifier.STATIC);
344 private static class EqualsMemberInfoModel implements MemberInfoModel {
345 MemberInfoTooltipManager myTooltipManager = new MemberInfoTooltipManager(new MemberInfoTooltipManager.TooltipProvider() {
346 public String getTooltip(MemberInfo memberInfo) {
347 if (checkForProblems(memberInfo) == OK) return null;
348 if (!(memberInfo.getMember() instanceof PsiField)) return CodeInsightBundle.message("generate.equals.hashcode.internal.error");
349 final PsiType type = ((PsiField)memberInfo.getMember()).getType();
350 if (GenerateEqualsHelper.isNestedArray(type)) {
351 return CodeInsightBundle .message("generate.equals.warning.equals.for.nested.arrays.not.supported");
353 if (GenerateEqualsHelper.isArrayOfObjects(type)) {
354 return CodeInsightBundle.message("generate.equals.warning.generated.equals.could.be.incorrect");
356 return null;
360 public boolean isMemberEnabled(MemberInfo member) {
361 if (!(member.getMember() instanceof PsiField)) return false;
362 final PsiType type = ((PsiField)member.getMember()).getType();
363 return !GenerateEqualsHelper.isNestedArray(type);
366 public boolean isCheckedWhenDisabled(MemberInfo member) {
367 return false;
370 public boolean isAbstractEnabled(MemberInfo member) {
371 return false;
374 public boolean isAbstractWhenDisabled(MemberInfo member) {
375 return false;
378 public Boolean isFixedAbstract(MemberInfo member) {
379 return null;
382 public int checkForProblems(@NotNull MemberInfo member) {
383 if (!(member.getMember() instanceof PsiField)) return ERROR;
384 final PsiType type = ((PsiField)member.getMember()).getType();
385 if (GenerateEqualsHelper.isNestedArray(type)) return ERROR;
386 if (GenerateEqualsHelper.isArrayOfObjects(type)) return WARNING;
387 return OK;
390 public void memberInfoChanged(MemberInfoChange event) {
393 public String getTooltipText(MemberInfo member) {
394 return myTooltipManager.getTooltip(member);
398 private static class HashCodeMemberInfoModel implements MemberInfoModel {
399 private final MemberInfoTooltipManager myTooltipManager = new MemberInfoTooltipManager(new MemberInfoTooltipManager.TooltipProvider() {
400 public String getTooltip(MemberInfo memberInfo) {
401 if (isMemberEnabled(memberInfo)) return null;
402 if (!(memberInfo.getMember() instanceof PsiField)) return CodeInsightBundle.message("generate.equals.hashcode.internal.error");
403 final PsiType type = ((PsiField)memberInfo.getMember()).getType();
404 if (!(type instanceof PsiArrayType)) return null;
405 return CodeInsightBundle.message("generate.equals.hashcode.warning.hashcode.for.arrays.is.not.supported");
409 public boolean isMemberEnabled(MemberInfo member) {
410 final PsiMember psiMember = member.getMember();
411 return psiMember instanceof PsiField;
414 public boolean isCheckedWhenDisabled(MemberInfo member) {
415 return false;
418 public boolean isAbstractEnabled(MemberInfo member) {
419 return false;
422 public boolean isAbstractWhenDisabled(MemberInfo member) {
423 return false;
426 public Boolean isFixedAbstract(MemberInfo member) {
427 return null;
430 public int checkForProblems(@NotNull MemberInfo member) {
431 return OK;
434 public void memberInfoChanged(MemberInfoChange event) {
437 public String getTooltipText(MemberInfo member) {
438 return myTooltipManager.getTooltip(member);