blob: e53c3a3b4ac7ea599776c7338759af996df6e177 [file] [log] [blame]
/*******************************************************************************
* 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
}
}