| /******************************************************************************* |
| * Copyright (c) 2008, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Mateusz Matela <mateusz.matela@gmail.com> - [code manipulation] [dcr] toString() builder wizard - https://bugs.eclipse.org/bugs/show_bug.cgi?id=26070 |
| * Mateusz Matela <mateusz.matela@gmail.com> - [toString] toString() generator: Fields in declaration order - https://bugs.eclipse.org/bugs/show_bug.cgi?id=279924 |
| * Pierre-Yves B. <pyvesdev@gmail.com> - Check whether enclosing instance implements hashCode and equals - https://bugs.eclipse.org/539900 |
| *******************************************************************************/ |
| package org.eclipse.jdt.ui.actions; |
| |
| |
| import java.lang.reflect.InvocationTargetException; |
| |
| import org.eclipse.swt.widgets.Shell; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| |
| import org.eclipse.jface.dialogs.Dialog; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.operation.IRunnableContext; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.window.Window; |
| |
| import org.eclipse.jface.text.IRewriteTarget; |
| import org.eclipse.jface.text.ITextSelection; |
| |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IWorkbenchSite; |
| import org.eclipse.ui.PlatformUI; |
| |
| import org.eclipse.ltk.core.refactoring.RefactoringStatus; |
| import org.eclipse.ltk.core.refactoring.RefactoringStatusContext; |
| import org.eclipse.ltk.ui.refactoring.RefactoringUI; |
| |
| import org.eclipse.jdt.core.IClassFile; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IMember; |
| import org.eclipse.jdt.core.IOpenable; |
| import org.eclipse.jdt.core.ISourceReference; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.ITypeBinding; |
| import org.eclipse.jdt.core.dom.Modifier; |
| import org.eclipse.jdt.core.dom.NodeFinder; |
| |
| import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; |
| import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings; |
| import org.eclipse.jdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; |
| import org.eclipse.jdt.internal.corext.fix.CleanUpConstants; |
| import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext; |
| import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; |
| import org.eclipse.jdt.internal.corext.util.JavaModelUtil; |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| |
| import org.eclipse.jdt.ui.JavaUI; |
| import org.eclipse.jdt.ui.PreferenceConstants; |
| import org.eclipse.jdt.ui.cleanup.CleanUpOptions; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.ui.actions.ActionMessages; |
| import org.eclipse.jdt.internal.ui.actions.ActionUtil; |
| import org.eclipse.jdt.internal.ui.actions.SelectionConverter; |
| import org.eclipse.jdt.internal.ui.actions.WorkbenchRunnableAdapter; |
| import org.eclipse.jdt.internal.ui.dialogs.SourceActionDialog; |
| import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor; |
| import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings; |
| import org.eclipse.jdt.internal.ui.util.BusyIndicatorRunnableContext; |
| import org.eclipse.jdt.internal.ui.util.ElementValidator; |
| import org.eclipse.jdt.internal.ui.util.ExceptionHandler; |
| |
| /** |
| * An abstract class containing elements common for <code>GenerateHashCodeEqualsAction</code> and |
| * <code>GenerateToStringAction</code> |
| * |
| * since 3.5 |
| */ |
| abstract class GenerateMethodAbstractAction extends SelectionDispatchAction { |
| |
| |
| CompilationUnitEditor fEditor; |
| CompilationUnit fUnit; |
| ITypeBinding fTypeBinding; |
| |
| protected GenerateMethodAbstractAction(IWorkbenchSite site) { |
| super(site); |
| } |
| |
| static RefactoringStatusContext createRefactoringStatusContext(IJavaElement element) { |
| if (element instanceof IMember) { |
| return JavaStatusContext.create((IMember) element); |
| } |
| if (element instanceof ISourceReference) { |
| IOpenable openable= element.getOpenable(); |
| try { |
| if (openable instanceof ICompilationUnit) { |
| return JavaStatusContext.create((ICompilationUnit) openable, ((ISourceReference) element).getSourceRange()); |
| } else if (openable instanceof IClassFile) { |
| return JavaStatusContext.create((IClassFile) openable, ((ISourceReference) element).getSourceRange()); |
| } |
| } catch (JavaModelException e) { |
| // ignore |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Can this action be enabled on the specified selection? |
| * |
| * @param selection the selection to test |
| * @return <code>true</code> if it can be enabled, <code>false</code> |
| * otherwise |
| * @throws JavaModelException if the kind of the selection cannot be |
| * determined |
| */ |
| boolean canEnable(final IStructuredSelection selection) throws JavaModelException { |
| if (selection.size() == 1) { |
| final Object element= selection.getFirstElement(); |
| if (element instanceof IType) { |
| final IType type= (IType) element; |
| return type.getCompilationUnit() != null && type.isClass(); |
| } |
| if (element instanceof ICompilationUnit) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the single selected type from the specified selection. |
| * |
| * @param selection the selection |
| * @return a single selected type, or <code>null</code> |
| * @throws JavaModelException if the kind of the selection cannot be |
| * determined |
| */ |
| IType getSelectedType(final IStructuredSelection selection) throws JavaModelException { |
| if (selection.size() == 1 && selection.getFirstElement() instanceof IType) { |
| final IType type= (IType) selection.getFirstElement(); |
| if (type.getCompilationUnit() != null && type.isClass()) |
| return type; |
| } else if (selection.getFirstElement() instanceof ICompilationUnit) { |
| final ICompilationUnit unit= (ICompilationUnit) selection.getFirstElement(); |
| final IType type= unit.findPrimaryType(); |
| if (type != null && type.isClass()) |
| return type; |
| } |
| return null; |
| } |
| |
| @Override |
| public void run(IStructuredSelection selection) { |
| try { |
| checkAndRun(getSelectedType(selection)); |
| } catch (CoreException exception) { |
| ExceptionHandler.handle(exception, getShell(), getErrorCaption(), ActionMessages.GenerateMethodAbstractAction_error_cannot_create); |
| } |
| } |
| |
| |
| @Override |
| public void run(ITextSelection selection) { |
| try { |
| checkAndRun(SelectionConverter.getTypeAtOffset(fEditor)); |
| } catch (CoreException e) { |
| ExceptionHandler.handle(e, getShell(), getErrorCaption(), ActionMessages.GenerateMethodAbstractAction_error_cannot_create); |
| } |
| } |
| |
| void checkAndRun(IType type) throws CoreException { |
| if (type == null) { |
| MessageDialog.openInformation(getShell(), getErrorCaption(), |
| ActionMessages.GenerateMethodAbstractAction_error_not_applicable); |
| notifyResult(false); |
| } |
| if (!ElementValidator.check(type, getShell(), getErrorCaption(), false) |
| || ! ActionUtil.isEditable(fEditor, getShell(), type)) { |
| notifyResult(false); |
| return; |
| } |
| if (type == null) { |
| MessageDialog.openError(getShell(), getErrorCaption(), ActionMessages.GenerateMethodAbstractAction_error_removed_type); |
| notifyResult(false); |
| return; |
| } |
| if (type.isAnnotation()) { |
| MessageDialog.openInformation(getShell(), getErrorCaption(), ActionMessages.GenerateMethodAbstractAction_annotation_not_applicable); |
| notifyResult(false); |
| return; |
| } |
| if (type.isInterface()) { |
| MessageDialog.openInformation(getShell(), getErrorCaption(), ActionMessages.GenerateMethodAbstractAction_interface_not_applicable); |
| notifyResult(false); |
| return; |
| } |
| if (type.isEnum()) { |
| MessageDialog.openInformation(getShell(), getErrorCaption(), ActionMessages.GenerateMethodAbstractAction_enum_not_applicable); |
| notifyResult(false); |
| return; |
| } |
| if (type.isAnonymous()) { |
| MessageDialog.openError(getShell(), getErrorCaption(), ActionMessages.GenerateMethodAbstractAction_anonymous_type_not_applicable); |
| notifyResult(false); |
| return; |
| } |
| run(getShell(), type); |
| } |
| |
| /** |
| * Runs the action. |
| * |
| * @param shell the shell to use |
| * @param type the type to generate stubs for |
| * @throws CoreException if an error occurs |
| */ |
| void run(Shell shell, IType type) throws CoreException { |
| |
| initialize(type); |
| |
| boolean regenerate= false; |
| if (isMethodAlreadyImplemented(fTypeBinding)) { |
| regenerate= MessageDialog.openQuestion(getShell(), getErrorCaption(), Messages.format(ActionMessages.GenerateMethodAbstractAction_already_has_this_method_error, new String[] { |
| BasicElementLabels.getJavaElementName(fTypeBinding.getQualifiedName()), getAlreadyImplementedErrorMethodName() })); |
| if (!regenerate) { |
| notifyResult(false); |
| return; |
| } |
| } |
| |
| if (!generateCandidates()) { |
| MessageDialog.openInformation(getShell(), getErrorCaption(), |
| getNoMembersError()); |
| notifyResult(false); |
| return; |
| } |
| |
| final SourceActionDialog dialog= createDialog(shell, type); |
| final int dialogResult= dialog.open(); |
| |
| if (dialogResult == Window.OK) { |
| |
| final Object[] selected= dialog.getResult(); |
| if (selected == null) { |
| notifyResult(false); |
| return; |
| } |
| final CodeGenerationSettings settings= createSettings(type, dialog); |
| final IWorkspaceRunnable operation= createOperation(selected, settings, regenerate, type, dialog.getElementPosition()); |
| |
| ITypeBinding superclass= fTypeBinding.getSuperclass(); |
| RefactoringStatus status= new RefactoringStatus(); |
| |
| status.merge(checkGeneralConditions(type, settings, selected)); |
| |
| if (!"java.lang.Object".equals(superclass.getQualifiedName())) { //$NON-NLS-1$ |
| status.merge(checkSuperClass(superclass)); |
| } |
| |
| if (fTypeBinding.isMember() && !Modifier.isStatic(fTypeBinding.getModifiers())) { |
| status.merge(checkEnclosingClass(fTypeBinding.getDeclaringClass())); |
| } |
| |
| for (Object s : selected) { |
| status.merge(checkMember(s)); |
| } |
| |
| if (status.hasEntries()) { |
| Dialog d= RefactoringUI.createLightWeightStatusDialog(status, getShell(), getErrorCaption()); |
| if (d.open() != Window.OK) { |
| notifyResult(false); |
| return; |
| } |
| } |
| |
| final IEditorPart editor= JavaUI.openInEditor(type.getCompilationUnit()); |
| final IRewriteTarget target= editor != null ? (IRewriteTarget) editor.getAdapter(IRewriteTarget.class) : null; |
| |
| if (target != null) |
| target.beginCompoundChange(); |
| try { |
| IRunnableContext context= JavaPlugin.getActiveWorkbenchWindow(); |
| if (context == null) |
| context= new BusyIndicatorRunnableContext(); |
| ISchedulingRule schedulingRule= ResourcesPlugin.getWorkspace().getRoot(); |
| PlatformUI.getWorkbench().getProgressService().runInUI(context, new WorkbenchRunnableAdapter(operation, schedulingRule), schedulingRule); |
| } catch (InvocationTargetException exception) { |
| ExceptionHandler.handle(exception, shell, getErrorCaption(), null); |
| } catch (InterruptedException exception) { |
| // Do nothing. Operation has been canceled by user. |
| } finally { |
| if (target != null) |
| target.endCompoundChange(); |
| } |
| } |
| notifyResult(dialogResult == Window.OK); |
| } |
| |
| |
| /** |
| * |
| * @param type the type for which a method is created |
| * @param dialog the dialog box where the user has defined his preferences |
| * @return settings applicable for this action |
| */ |
| CodeGenerationSettings createSettings(IType type, SourceActionDialog dialog) { |
| CodeGenerationSettings settings= JavaPreferencesSettings.getCodeGenerationSettings(type.getJavaProject()); |
| settings.createComments= dialog.getGenerateComment(); |
| return settings; |
| } |
| |
| /** |
| * @return Message to be shown when a method cannot be generated due to lack of appropriate members |
| */ |
| abstract String getNoMembersError(); |
| |
| /** |
| * @return Caption for a dialog with error message |
| */ |
| abstract String getErrorCaption(); |
| |
| abstract IWorkspaceRunnable createOperation(Object[] selectedBindings, CodeGenerationSettings settings, boolean regenerate, IJavaElement type, IJavaElement elementPosition); |
| |
| /** |
| * Checks whether general requirements are fulfilled |
| * |
| * @param type the type for which a method is created |
| * @param settings preferences define by the user |
| * @param selected the type's members selected to be used in generated method |
| * @return RefactoringStatus containing information about eventual problems |
| */ |
| abstract RefactoringStatus checkGeneralConditions(IType type, CodeGenerationSettings settings, Object[] selected); |
| |
| /** |
| * Checks whether a member fulfills requirements expected by method generator |
| * |
| * @param object member binding to be checked |
| * @return RefactoringStatus containing information about eventual problems |
| */ |
| abstract RefactoringStatus checkMember(Object object); |
| |
| /** |
| * Checks whether the superclass fulfills requirements expected by method generator |
| * @param superclass superclass type binding to be checked |
| * @return RefactoringStatus containing information about eventual problems |
| */ |
| abstract RefactoringStatus checkSuperClass(ITypeBinding superclass); |
| |
| /** |
| * Checks whether the enclosing class fulfills requirements expected by method generator |
| * @param enclosingClass enclosing class type binding to be checked |
| * @return RefactoringStatus containing information about eventual problems |
| */ |
| abstract RefactoringStatus checkEnclosingClass(ITypeBinding enclosingClass); |
| |
| /** |
| * Creates the action's main dialog. |
| * |
| * @param shell the shell to use |
| * @param type the type to generate stubs for |
| * @return a dialog with generator-specific options |
| * @throws JavaModelException if creation of the dialog fails |
| */ |
| abstract SourceActionDialog createDialog(Shell shell, IType type) throws JavaModelException; |
| |
| /** |
| * Chooses type members that can be used in generated method. |
| * Returns false, if there are no such members and the method cannot |
| * be generated |
| * @return true, if the method can be generated (i.e. there are appropriate member fields) |
| * @throws JavaModelException if an error in java model occurs |
| */ |
| abstract boolean generateCandidates() throws JavaModelException; |
| |
| /** |
| * |
| * @return The message displayed when the method is already implemented in the type. |
| */ |
| abstract String getAlreadyImplementedErrorMethodName(); |
| |
| /** |
| * |
| * @param typeBinding Type to be checked |
| * @return true if given type already contains the method to be generated |
| */ |
| abstract boolean isMethodAlreadyImplemented(ITypeBinding typeBinding); |
| |
| boolean useBlocks(IJavaProject project) { |
| if (CleanUpOptions.TRUE.equals(PreferenceConstants.getPreference(CleanUpConstants.CONTROL_STATEMENTS_USE_BLOCKS, project))) { |
| return CleanUpOptions.TRUE.equals(PreferenceConstants.getPreference(CleanUpConstants.CONTROL_STATEMENTS_USE_BLOCKS_ALWAYS, project)) |
| || CleanUpOptions.TRUE.equals(PreferenceConstants.getPreference(CleanUpConstants.CONTROL_STATEMENTS_USE_BLOCKS_NO_FOR_RETURN_AND_THROW, project)); |
| } |
| return false; |
| } |
| |
| void initialize(IType type) throws JavaModelException { |
| RefactoringASTParser parser= new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL); |
| fUnit= parser.parse(type.getCompilationUnit(), true); |
| fTypeBinding= null; |
| // type cannot be anonymous |
| final AbstractTypeDeclaration declaration= ASTNodes.getParent(NodeFinder.perform(fUnit, type.getNameRange()), |
| AbstractTypeDeclaration.class); |
| if (declaration != null) |
| fTypeBinding= declaration.resolveBinding(); |
| } |
| |
| @Override |
| public void selectionChanged(IStructuredSelection selection) { |
| try { |
| setEnabled(canEnable(selection)); |
| } catch (JavaModelException exception) { |
| if (JavaModelUtil.isExceptionToBeLogged(exception)) |
| JavaPlugin.log(exception); |
| setEnabled(false); |
| } |
| } |
| |
| @Override |
| public void selectionChanged(ITextSelection selection) { |
| // Do nothing |
| } |
| |
| } |