/******************************************************************************* * Copyright (c) 2008, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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 *******************************************************************************/ 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.AST; 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.NodeFinder; import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.fix.CleanUpConstants; import org.eclipse.jdt.internal.corext.refactoring.base.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; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; /** * 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; } public void run(IStructuredSelection selection) { try { checkAndRun(getSelectedType(selection)); } catch (CoreException exception) { ExceptionHandler.handle(exception, getShell(), getErrorCaption(), ActionMessages.GenerateMethodAbstractAction_error_cannot_create); } } 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)); } for (int i= 0; i < selected.length; i++) { status.merge(checkMember(selected[i])); } 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); /** * 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_STATMENTS_USE_BLOCKS_ALWAYS, project)) || CleanUpOptions.TRUE.equals(PreferenceConstants.getPreference(CleanUpConstants.CONTROL_STATMENTS_USE_BLOCKS_NO_FOR_RETURN_AND_THROW, project)); } return false; } void initialize(IType type) throws JavaModelException { RefactoringASTParser parser= new RefactoringASTParser(AST.JLS3); fUnit= parser.parse(type.getCompilationUnit(), true); fTypeBinding= null; // type cannot be anonymous final AbstractTypeDeclaration declaration= (AbstractTypeDeclaration) ASTNodes.getParent(NodeFinder.perform(fUnit, type.getNameRange()), AbstractTypeDeclaration.class); if (declaration != null) fTypeBinding= declaration.resolveBinding(); } public void selectionChanged(IStructuredSelection selection) { try { setEnabled(canEnable(selection)); } catch (JavaModelException exception) { if (JavaModelUtil.isExceptionToBeLogged(exception)) JavaPlugin.log(exception); setEnabled(false); } } public void selectionChanged(ITextSelection selection) { // Do nothing } }