KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > ui > actions > GenerateHashCodeEqualsAction


1 /*******************************************************************************
2  * Copyright (c) 2005, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jdt.ui.actions;
12
13 import java.lang.reflect.InvocationTargetException JavaDoc;
14 import java.util.ArrayList JavaDoc;
15 import java.util.Arrays JavaDoc;
16 import java.util.List JavaDoc;
17
18 import org.eclipse.core.runtime.CoreException;
19
20 import org.eclipse.swt.widgets.Shell;
21
22 import org.eclipse.jface.dialogs.Dialog;
23 import org.eclipse.jface.dialogs.MessageDialog;
24 import org.eclipse.jface.operation.IRunnableContext;
25 import org.eclipse.jface.viewers.IStructuredSelection;
26 import org.eclipse.jface.window.Window;
27
28 import org.eclipse.jface.text.IRewriteTarget;
29 import org.eclipse.jface.text.ITextSelection;
30
31 import org.eclipse.ui.IEditorPart;
32 import org.eclipse.ui.IWorkbenchSite;
33 import org.eclipse.ui.PlatformUI;
34
35 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
36 import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
37 import org.eclipse.ltk.ui.refactoring.RefactoringUI;
38
39 import org.eclipse.jdt.core.IClassFile;
40 import org.eclipse.jdt.core.ICompilationUnit;
41 import org.eclipse.jdt.core.IJavaElement;
42 import org.eclipse.jdt.core.IMember;
43 import org.eclipse.jdt.core.IOpenable;
44 import org.eclipse.jdt.core.ISourceReference;
45 import org.eclipse.jdt.core.IType;
46 import org.eclipse.jdt.core.JavaModelException;
47 import org.eclipse.jdt.core.dom.AST;
48 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
49 import org.eclipse.jdt.core.dom.CompilationUnit;
50 import org.eclipse.jdt.core.dom.IMethodBinding;
51 import org.eclipse.jdt.core.dom.ITypeBinding;
52 import org.eclipse.jdt.core.dom.IVariableBinding;
53 import org.eclipse.jdt.core.dom.Modifier;
54
55 import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
56 import org.eclipse.jdt.internal.corext.codemanipulation.GenerateHashCodeEqualsOperation;
57 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
58 import org.eclipse.jdt.internal.corext.dom.NodeFinder;
59 import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext;
60 import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
61 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
62 import org.eclipse.jdt.internal.corext.util.Messages;
63
64 import org.eclipse.jdt.ui.JavaUI;
65
66 import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
67 import org.eclipse.jdt.internal.ui.JavaPlugin;
68 import org.eclipse.jdt.internal.ui.actions.ActionMessages;
69 import org.eclipse.jdt.internal.ui.actions.ActionUtil;
70 import org.eclipse.jdt.internal.ui.actions.SelectionConverter;
71 import org.eclipse.jdt.internal.ui.actions.WorkbenchRunnableAdapter;
72 import org.eclipse.jdt.internal.ui.dialogs.GenerateHashCodeEqualsDialog;
73 import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
74 import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings;
75 import org.eclipse.jdt.internal.ui.util.BusyIndicatorRunnableContext;
76 import org.eclipse.jdt.internal.ui.util.ElementValidator;
77 import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
78
79 /**
80  * Adds method implementations for
81  * <code>{@link java.lang.Object#equals(java.lang.Object)}</code> and
82  * <code>{@link java.lang.Object#hashCode()}</code>. The action opens a
83  * dialog from which the user can choose the fields to be considered.
84  * <p>
85  * Will open the parent compilation unit in a Java editor. The result is
86  * unsaved, so the user can decide if the changes are acceptable.
87  * <p>
88  * The action is applicable to structured selections containing elements of type
89  * {@link org.eclipse.jdt.core.IType}.
90  *
91  * <p>
92  * This class may be instantiated; it is not intended to be subclassed.
93  * </p>
94  *
95  * @since 3.2
96  */

97 public final class GenerateHashCodeEqualsAction extends SelectionDispatchAction {
98
99     private static final String JavaDoc METHODNAME_HASH_CODE= "hashCode"; //$NON-NLS-1$
100

101     private static final String JavaDoc METHODNAME_EQUALS= "equals"; //$NON-NLS-1$
102

103     private CompilationUnitEditor fEditor;
104
105     private CompilationUnit fUnit;
106
107     private ITypeBinding fTypeBinding;
108
109     private IVariableBinding[] fCandidateFields;
110
111     private class HashCodeEqualsInfo {
112         
113         public boolean foundHashCode= false;
114
115         public boolean foundEquals= false;
116
117         public boolean foundFinalHashCode= false;
118
119         public boolean foundFinalEquals= false;
120     }
121
122     /**
123      * Note: This constructor is for internal use only. Clients should not call
124      * this constructor.
125      *
126      * @param editor the compilation unit editor
127      */

128     public GenerateHashCodeEqualsAction(final CompilationUnitEditor editor) {
129         this(editor.getEditorSite());
130         fEditor= editor;
131         setEnabled( (fEditor != null && SelectionConverter.canOperateOn(fEditor)));
132     }
133
134     /**
135      * Creates a new generate hashCode equals action.
136      * <p>
137      * The action requires that the selection provided by the site's selection
138      * provider is of type
139      * {@link org.eclipse.jface.viewers.IStructuredSelection}.
140      *
141      * @param site the workbench site providing context information for this
142      * action
143      */

144     public GenerateHashCodeEqualsAction(final IWorkbenchSite site) {
145         super(site);
146         setText(ActionMessages.GenerateHashCodeEqualsAction_label);
147         setDescription(ActionMessages.GenerateHashCodeEqualsAction_description);
148         setToolTipText(ActionMessages.GenerateHashCodeEqualsAction_tooltip);
149         PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.GENERATE_HASHCODE_EQUALS_ACTION);
150     }
151
152     /**
153      * Can this action be enabled on the specified selection?
154      *
155      * @param selection the selection to test
156      * @return <code>true</code> if it can be enabled, <code>false</code>
157      * otherwise
158      * @throws JavaModelException if the kind of the selection cannot be
159      * determined
160      */

161     private boolean canEnable(final IStructuredSelection selection) throws JavaModelException {
162         if (selection.size() == 1) {
163             final Object JavaDoc element= selection.getFirstElement();
164             if (element instanceof IType) {
165                 final IType type= (IType) element;
166                 return type.getCompilationUnit() != null && type.isClass();
167             }
168             if (element instanceof ICompilationUnit)
169                 return true;
170         }
171         return false;
172     }
173
174     /**
175      * Returns the single selected type from the specified selection.
176      *
177      * @param selection the selection
178      * @return a single selected type, or <code>null</code>
179      * @throws JavaModelException if the kind of the selection cannot be
180      * determined
181      */

182     private IType getSelectedType(final IStructuredSelection selection) throws JavaModelException {
183         if (selection.size() == 1 && selection.getFirstElement() instanceof IType) {
184             final IType type= (IType) selection.getFirstElement();
185             if (type.getCompilationUnit() != null && type.isClass())
186                 return type;
187         } else if (selection.getFirstElement() instanceof ICompilationUnit) {
188             final ICompilationUnit unit= (ICompilationUnit) selection.getFirstElement();
189             final IType type= unit.findPrimaryType();
190             if (type != null && type.isClass())
191                 return type;
192         }
193         return null;
194     }
195
196     /*
197      * @see org.eclipse.jdt.ui.actions.SelectionDispatchAction#run(org.eclipse.jface.viewers.IStructuredSelection)
198      */

199     public void run(IStructuredSelection selection) {
200         try {
201             checkAndRun(getSelectedType(selection));
202         } catch (CoreException exception) {
203             ExceptionHandler.handle(exception, getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
204                     ActionMessages.GenerateHashCodeEqualsAction_error_cannot_create);
205         }
206     }
207
208     /*
209      * @see org.eclipse.jdt.ui.actions.SelectionDispatchAction#run(org.eclipse.jface.text.ITextSelection)
210      */

211     public void run(ITextSelection selection) {
212         try {
213             checkAndRun(SelectionConverter.getTypeAtOffset(fEditor));
214         } catch (CoreException e) {
215             ExceptionHandler.handle(e, getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
216                     ActionMessages.GenerateHashCodeEqualsAction_error_cannot_create);
217         }
218     }
219
220     private void checkAndRun(IType type) throws CoreException {
221         if (type == null) {
222             MessageDialog.openInformation(getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
223                     ActionMessages.GenerateHashCodeEqualsAction_error_not_applicable);
224             notifyResult(false);
225         }
226         if (!ElementValidator.check(type, getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption, false)
227                 || ! ActionUtil.isEditable(fEditor, getShell(), type)) {
228             notifyResult(false);
229             return;
230         }
231         if (type == null) {
232             MessageDialog.openError(getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
233                     ActionMessages.GenerateHashCodeEqualsAction_error_removed_type);
234             notifyResult(false);
235             return;
236         }
237         if (type.isAnnotation()) {
238             MessageDialog.openInformation(getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
239                     ActionMessages.GenerateHashCodeEqualsAction_annotation_not_applicable);
240             notifyResult(false);
241             return;
242         }
243         if (type.isInterface()) {
244             MessageDialog.openInformation(getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
245                     ActionMessages.GenerateHashCodeEqualsAction_interface_not_applicable);
246             notifyResult(false);
247             return;
248         }
249         if (type.isEnum()) {
250             MessageDialog.openInformation(getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
251                     ActionMessages.GenerateHashCodeEqualsAction_enum_not_applicable);
252             notifyResult(false);
253             return;
254         }
255         if (type.isAnonymous()) {
256             MessageDialog.openError(getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
257                     ActionMessages.GenerateHashCodeEqualsAction_anonymous_type_not_applicable);
258             notifyResult(false);
259             return;
260         }
261         run(getShell(), type);
262     }
263
264     /**
265      * Runs the action.
266      *
267      * @param shell the shell to use
268      * @param type the type to generate stubs for
269      * @throws CoreException if an error occurs
270      */

271     private void run(Shell shell, IType type) throws CoreException {
272
273         initialize(type);
274
275         boolean regenerate= false;
276         if (hasHashCodeOrEquals(fTypeBinding)) {
277             regenerate= MessageDialog.openQuestion(getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption, Messages.format(ActionMessages.GenerateHashCodeEqualsAction_already_has_hashCode_equals_error, fTypeBinding.getQualifiedName()));
278             if (!regenerate) {
279                 notifyResult(false);
280                 return;
281             }
282         }
283
284         List JavaDoc allFields= new ArrayList JavaDoc();
285         List JavaDoc selectedFields= new ArrayList JavaDoc();
286         for (int i= 0; i < fCandidateFields.length; i++) {
287             if (!Modifier.isStatic(fCandidateFields[i].getModifiers())) {
288                 allFields.add(fCandidateFields[i]);
289                 if (!Modifier.isTransient(fCandidateFields[i].getModifiers()))
290                     selectedFields.add(fCandidateFields[i]);
291             }
292         }
293
294         if (allFields.isEmpty()) {
295             MessageDialog.openInformation(getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption,
296                     ActionMessages.GenerateHashCodeEqualsAction_no_nonstatic_fields_error);
297             notifyResult(false);
298             return;
299         }
300
301         IVariableBinding[] allFieldBindings= (IVariableBinding[]) allFields.toArray(new IVariableBinding[0]);
302         IVariableBinding[] selectedFieldBindings= (IVariableBinding[]) selectedFields.toArray(new IVariableBinding[0]);
303
304         final GenerateHashCodeEqualsDialog dialog= new GenerateHashCodeEqualsDialog(shell, fEditor, type, allFieldBindings, selectedFieldBindings);
305         final int dialogResult= dialog.open();
306         if (dialogResult == Window.OK) {
307
308             final Object JavaDoc[] selected= dialog.getResult();
309             if (selected == null) {
310                 notifyResult(false);
311                 return;
312             }
313
314             final IVariableBinding[] selectedBindings= (IVariableBinding[]) Arrays.asList(selected).toArray(new IVariableBinding[0]);
315
316             ITypeBinding superclass= fTypeBinding.getSuperclass();
317             RefactoringStatus status= new RefactoringStatus();
318             ArrayList JavaDoc alreadyChecked= new ArrayList JavaDoc();
319
320             if (!"java.lang.Object".equals(superclass.getQualifiedName())) { //$NON-NLS-1$
321
status.merge(checkHashCodeEqualsExists(superclass, true));
322             }
323
324             for (int i= 0; i < selectedBindings.length; i++) {
325                 ITypeBinding fieldsType= selectedBindings[i].getType();
326                 if (fieldsType.isArray())
327                     fieldsType= fieldsType.getElementType();
328                 if (!fieldsType.isPrimitive() && !fieldsType.isEnum() && !alreadyChecked.contains(fieldsType) && !fieldsType.equals(fTypeBinding)) {
329                     status.merge(checkHashCodeEqualsExists(fieldsType, false));
330                     alreadyChecked.add(fieldsType);
331                 }
332                 if (Modifier.isTransient(selectedBindings[i].getModifiers()))
333                     status.addWarning(Messages.format(ActionMessages.GenerateHashCodeEqualsAction_transient_field_included_error, selectedBindings[i]
334                             .getName()), createRefactoringStatusContext(selectedBindings[i].getJavaElement()));
335             }
336
337             if (status.hasEntries()) {
338                 Dialog d= RefactoringUI.createLightWeightStatusDialog(status, getShell(), ActionMessages.GenerateHashCodeEqualsAction_error_caption);
339                 if (d.open() != Window.OK) {
340                     notifyResult(false);
341                     return;
342                 }
343             }
344
345             final CodeGenerationSettings settings= JavaPreferencesSettings.getCodeGenerationSettings(type.getJavaProject());
346             settings.createComments= dialog.getGenerateComment();
347             final IEditorPart editor= JavaUI.openInEditor(type.getCompilationUnit());
348             final IRewriteTarget target= editor != null ? (IRewriteTarget) editor.getAdapter(IRewriteTarget.class) : null;
349
350             if (target != null)
351                 target.beginCompoundChange();
352             try {
353                 final GenerateHashCodeEqualsOperation operation= new GenerateHashCodeEqualsOperation(fTypeBinding, selectedBindings, fUnit, dialog
354                         .getElementPosition(), settings, dialog.isUseInstanceOf(), regenerate, true, false);
355                 IRunnableContext context= JavaPlugin.getActiveWorkbenchWindow();
356                 if (context == null)
357                     context= new BusyIndicatorRunnableContext();
358                 PlatformUI.getWorkbench().getProgressService().runInUI(context,
359                         new WorkbenchRunnableAdapter(operation, operation.getSchedulingRule()), operation.getSchedulingRule());
360             } catch (InvocationTargetException JavaDoc exception) {
361                 ExceptionHandler.handle(exception, shell, ActionMessages.GenerateHashCodeEqualsAction_error_caption, null);
362             } catch (InterruptedException JavaDoc exception) {
363                 // Do nothing. Operation has been canceled by user.
364
} finally {
365                 if (target != null)
366                     target.endCompoundChange();
367             }
368         }
369         notifyResult(dialogResult == Window.OK);
370     }
371     
372     private static RefactoringStatusContext createRefactoringStatusContext(IJavaElement element) {
373         if (element instanceof IMember) {
374             return JavaStatusContext.create((IMember) element);
375         }
376         if (element instanceof ISourceReference) {
377             IOpenable openable= element.getOpenable();
378             try {
379                 if (openable instanceof ICompilationUnit) {
380                     return JavaStatusContext.create((ICompilationUnit) openable, ((ISourceReference) element).getSourceRange());
381                 } else if (openable instanceof IClassFile) {
382                     return JavaStatusContext.create((IClassFile) openable, ((ISourceReference) element).getSourceRange());
383                 }
384             } catch (JavaModelException e) {
385                 // ignore
386
}
387         }
388         return null;
389     }
390
391     private boolean hasHashCodeOrEquals(ITypeBinding someType) {
392         HashCodeEqualsInfo info= getTypeInfo(someType);
393         return (info.foundEquals || info.foundHashCode);
394     }
395
396     private RefactoringStatus checkHashCodeEqualsExists(ITypeBinding someType, boolean superClass) {
397
398         RefactoringStatus status= new RefactoringStatus();
399         HashCodeEqualsInfo info= getTypeInfo(someType);
400
401         String JavaDoc concreteTypeWarning= superClass
402                 ? ActionMessages.GenerateHashCodeEqualsAction_super_class
403                 : ActionMessages.GenerateHashCodeEqualsAction_field_type;
404         String JavaDoc concreteMethWarning= (someType.isInterface() || Modifier.isAbstract(someType.getModifiers()))
405                 ? ActionMessages.GenerateHashCodeEqualsAction_interface_does_not_declare_hashCode_equals_error
406                 : ActionMessages.GenerateHashCodeEqualsAction_type_does_not_implement_hashCode_equals_error;
407         String JavaDoc concreteHCEWarning= null;
408
409         if (!info.foundEquals && (!info.foundHashCode))
410             concreteHCEWarning= ActionMessages.GenerateHashCodeEqualsAction_equals_and_hashCode;
411         else if (!info.foundEquals)
412             concreteHCEWarning= ActionMessages.GenerateHashCodeEqualsAction_equals;
413         else if (!info.foundHashCode)
414             concreteHCEWarning= ActionMessages.GenerateHashCodeEqualsAction_hashCode;
415
416         if (!info.foundEquals && !info.foundHashCode)
417             status.addWarning(Messages.format(concreteMethWarning, new String JavaDoc[] { Messages.format(concreteTypeWarning, someType.getQualifiedName()),
418                     concreteHCEWarning }), createRefactoringStatusContext(someType.getJavaElement()));
419
420         if (superClass && (info.foundFinalEquals || info.foundFinalHashCode)) {
421             status.addError(Messages.format(ActionMessages.GenerateHashCodeEqualsAction_final_hashCode_equals_in_superclass_error, Messages.format(
422                     concreteTypeWarning, someType.getQualifiedName())), createRefactoringStatusContext(someType.getJavaElement()));
423         }
424
425         return status;
426     }
427
428     private HashCodeEqualsInfo getTypeInfo(ITypeBinding someType) {
429         HashCodeEqualsInfo info= new HashCodeEqualsInfo();
430         if (someType.isTypeVariable()) {
431             someType= someType.getErasure();
432         }
433         
434         IMethodBinding[] declaredMethods= someType.getDeclaredMethods();
435
436         for (int i= 0; i < declaredMethods.length; i++) {
437             if (declaredMethods[i].getName().equals(METHODNAME_EQUALS)) {
438                 ITypeBinding[] b= declaredMethods[i].getParameterTypes();
439                 if ( (b.length == 1) && (b[0].getQualifiedName().equals("java.lang.Object"))) { //$NON-NLS-1$
440
info.foundEquals= true;
441                     if (Modifier.isFinal(declaredMethods[i].getModifiers()))
442                         info.foundFinalEquals= true;
443                 }
444             }
445             if (declaredMethods[i].getName().equals(METHODNAME_HASH_CODE) && declaredMethods[i].getParameterTypes().length == 0) {
446                 info.foundHashCode= true;
447                 if (Modifier.isFinal(declaredMethods[i].getModifiers()))
448
449                     info.foundFinalHashCode= true;
450             }
451             if (info.foundEquals && info.foundHashCode)
452                 break;
453         }
454         return info;
455     }
456
457     private void initialize(IType type) throws JavaModelException {
458         RefactoringASTParser parser= new RefactoringASTParser(AST.JLS3);
459         fUnit= parser.parse(type.getCompilationUnit(), true);
460         fTypeBinding= null;
461         // type cannot be anonymous
462
final AbstractTypeDeclaration declaration= (AbstractTypeDeclaration) ASTNodes.getParent(NodeFinder.perform(fUnit, type.getNameRange()),
463                 AbstractTypeDeclaration.class);
464         if (declaration != null)
465             fTypeBinding= declaration.resolveBinding();
466
467         fCandidateFields= fTypeBinding.getDeclaredFields();
468     }
469
470     /*
471      * @see org.eclipse.jdt.ui.actions.SelectionDispatchAction#selectionChanged(org.eclipse.jface.viewers.IStructuredSelection)
472      */

473     public void selectionChanged(IStructuredSelection selection) {
474         try {
475             setEnabled(canEnable(selection));
476         } catch (JavaModelException exception) {
477             if (JavaModelUtil.isExceptionToBeLogged(exception))
478                 JavaPlugin.log(exception);
479             setEnabled(false);
480         }
481     }
482
483     /*
484      * @see org.eclipse.jdt.ui.actions.SelectionDispatchAction#run(org.eclipse.jface.text.ITextSelection)
485      */

486     public void selectionChanged(ITextSelection selection) {
487         // Do nothing
488
}
489 }
490
Popular Tags