blob: b60772f1d98cee9ebaef4802dc514db6465571c3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2011 Mateusz Matela 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:
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.ui.IWorkbenchSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
import org.eclipse.jdt.internal.corext.codemanipulation.tostringgeneration.GenerateToStringOperation;
import org.eclipse.jdt.internal.corext.codemanipulation.tostringgeneration.ToStringGenerationSettings;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.actions.ActionMessages;
import org.eclipse.jdt.internal.ui.actions.SelectionConverter;
import org.eclipse.jdt.internal.ui.dialogs.GenerateToStringDialog;
import org.eclipse.jdt.internal.ui.dialogs.SourceActionDialog;
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
/**
* Adds method implementations for <code>{@link java.lang.Object#toString()}</code> The action opens a
* dialog from which the user can choose the fields and methods to be considered.
* <p>
* Will open the parent compilation unit in a Java editor. The result is
* unsaved, so the user can decide if the changes are acceptable.
* <p>
* The action is applicable to structured selections containing elements of type
* {@link org.eclipse.jdt.core.IType}.
*
* <p>
* This class may be instantiated; it is not intended to be subclassed.
* </p>
*
* @since 3.5
*
* @noextend This class is not intended to be subclassed by clients.
*/
public class GenerateToStringAction extends GenerateMethodAbstractAction {
private static final String METHODNAME_TO_STRING= "toString"; //$NON-NLS-1$
private List<IVariableBinding> fFields;
private List<IVariableBinding> fInheritedFields;
private List<IVariableBinding> fSelectedFields;
private List<IMethodBinding> fMethods;
private List<IMethodBinding> fInheritedMethods;
private GenerateToStringOperation operation;
private class ToStringInfo {
public boolean foundToString= false;
public boolean foundFinalToString= false;
public ToStringInfo(ITypeBinding typeBinding) {
IMethodBinding[] declaredMethods= typeBinding.getDeclaredMethods();
for (int i= 0; i < declaredMethods.length; i++) {
if (declaredMethods[i].getName().equals(METHODNAME_TO_STRING) && declaredMethods[i].getParameterTypes().length == 0) {
this.foundToString= true;
if (Modifier.isFinal(declaredMethods[i].getModifiers()))
this.foundFinalToString= true;
}
}
}
}
/**
* Creates a new generate tostring action.
* <p>
* The action requires that the selection provided by the site's selection
* provider is of type
* {@link org.eclipse.jface.viewers.IStructuredSelection}.
*
* @param site the workbench site providing context information for this
* action
*/
public GenerateToStringAction(IWorkbenchSite site) {
super(site);
setText(ActionMessages.GenerateToStringAction_label);
setDescription(ActionMessages.GenerateToStringAction_description);
setToolTipText(ActionMessages.GenerateToStringAction_tooltip);
PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.GENERATE_TOSTRING_ACTION);
}
/**
* Note: This constructor is for internal use only. Clients should not call
* this constructor.
*
* @param editor the compilation unit editor
*
* @noreference This constructor is not intended to be referenced by clients.
*/
public GenerateToStringAction(CompilationUnitEditor editor) {
this(editor.getEditorSite());
fEditor= editor;
setEnabled((fEditor != null && SelectionConverter.canOperateOn(fEditor)));
}
@Override
RefactoringStatus checkMember(Object object) {
// no conditions need to be checked
return new RefactoringStatus();
}
@Override
RefactoringStatus checkGeneralConditions(IType type, CodeGenerationSettings settings, Object[] selected) {
return operation.checkConditions();
}
@Override
RefactoringStatus checkSuperClass(ITypeBinding superclass) {
RefactoringStatus status= new RefactoringStatus();
if (new ToStringInfo(superclass).foundFinalToString) {
status.addError(Messages.format(ActionMessages.GenerateMethodAbstractAction_final_method_in_superclass_error, new String[] {
Messages.format(ActionMessages.GenerateMethodAbstractAction_super_class, BasicElementLabels.getJavaElementName(superclass.getQualifiedName())),
ActionMessages.GenerateToStringAction_tostring }), createRefactoringStatusContext(superclass.getJavaElement()));
}
return status;
}
@Override
SourceActionDialog createDialog(Shell shell, IType type) throws JavaModelException {
IVariableBinding[] fieldBindings= fFields.toArray(new IVariableBinding[0]);
IVariableBinding[] inheritedFieldBindings= fInheritedFields.toArray(new IVariableBinding[0]);
IVariableBinding[] selectedFieldBindings= fSelectedFields.toArray(new IVariableBinding[0]);
IMethodBinding[] methodBindings= fMethods.toArray(new IMethodBinding[0]);
IMethodBinding[] inheritededMethodBindings= fInheritedMethods.toArray(new IMethodBinding[0]);
return new GenerateToStringDialog(shell, fEditor, type, fieldBindings, inheritedFieldBindings, selectedFieldBindings, methodBindings, inheritededMethodBindings);
}
@Override
IWorkspaceRunnable createOperation(Object[] selectedBindings, CodeGenerationSettings settings, boolean regenerate, IJavaElement type, IJavaElement elementPosition) {
return operation= GenerateToStringOperation.createOperation(fTypeBinding, selectedBindings, fUnit, elementPosition, (ToStringGenerationSettings)settings);
}
@Override
CodeGenerationSettings createSettings(IType type, SourceActionDialog dialog) {
ToStringGenerationSettings settings= ((GenerateToStringDialog) dialog).getGenerationSettings();
super.createSettings(type, dialog).setSettings(settings);
settings.createComments= dialog.getGenerateComment();
settings.useBlocks= useBlocks(type.getJavaProject());
String version= fUnit.getJavaElement().getJavaProject().getOption(JavaCore.COMPILER_SOURCE, true);
settings.is50orHigher= !JavaModelUtil.isVersionLessThan(version, JavaCore.VERSION_1_5);
settings.is60orHigher= !JavaModelUtil.isVersionLessThan(version, JavaCore.VERSION_1_6);
return settings;
}
@Override
boolean generateCandidates() throws JavaModelException {
IVariableBinding[] candidateFields= fTypeBinding.getDeclaredFields();
HashMap<IJavaElement, IVariableBinding> fieldsToBindings= new HashMap<IJavaElement, IVariableBinding>();
HashMap<IJavaElement, IVariableBinding> selectedFieldsToBindings= new HashMap<IJavaElement, IVariableBinding>();
for (int i= 0; i < candidateFields.length; i++) {
if (!Modifier.isStatic(candidateFields[i].getModifiers())) {
fieldsToBindings.put(candidateFields[i].getJavaElement(), candidateFields[i]);
if (!Modifier.isTransient(candidateFields[i].getModifiers()))
selectedFieldsToBindings.put(candidateFields[i].getJavaElement(), candidateFields[i]);
}
}
IType type= (IType)fTypeBinding.getJavaElement();
IField[] allFields= type.getFields();
fFields= new ArrayList<IVariableBinding>();
populateMembers(fFields, allFields, fieldsToBindings);
fSelectedFields= new ArrayList<IVariableBinding>();
populateMembers(fSelectedFields, allFields, selectedFieldsToBindings);
IMethodBinding[] candidateMethods= fTypeBinding.getDeclaredMethods();
HashMap<IJavaElement, IMethodBinding> methodsToBindings= new HashMap<IJavaElement, IMethodBinding>();
for (int i= 0; i < candidateMethods.length; i++) {
if (!Modifier.isStatic(candidateMethods[i].getModifiers()) && candidateMethods[i].getParameterTypes().length == 0
&& !candidateMethods[i].getReturnType().getName().equals("void") && !candidateMethods[i].getName().equals("toString") && !candidateMethods[i].getName().equals("clone")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
methodsToBindings.put(candidateMethods[i].getJavaElement(), candidateMethods[i]);
}
}
fMethods= new ArrayList<IMethodBinding>();
populateMembers(fMethods, type.getMethods(), methodsToBindings);
fInheritedFields= new ArrayList<IVariableBinding>();
fInheritedMethods= new ArrayList<IMethodBinding>();
ITypeBinding typeBinding= fTypeBinding;
while ((typeBinding= typeBinding.getSuperclass()) != null) {
type = (IType)typeBinding.getJavaElement();
candidateFields= typeBinding.getDeclaredFields();
for (int i= 0; i < candidateFields.length; i++) {
if (!Modifier.isPrivate(candidateFields[i].getModifiers()) && !Modifier.isStatic(candidateFields[i].getModifiers()) && !contains(fFields, candidateFields[i])
&& !contains(fInheritedFields, candidateFields[i])) {
fieldsToBindings.put(candidateFields[i].getJavaElement(), candidateFields[i]);
}
}
populateMembers(fInheritedFields, type.getFields(), fieldsToBindings);
candidateMethods= typeBinding.getDeclaredMethods();
for (int i= 0; i < candidateMethods.length; i++) {
if (!Modifier.isPrivate(candidateMethods[i].getModifiers())
&& !Modifier.isStatic(candidateMethods[i].getModifiers())
&& candidateMethods[i].getParameterTypes().length == 0
&& !candidateMethods[i].getReturnType().getName().equals("void") && !contains(fMethods, candidateMethods[i]) && !contains(fInheritedMethods, candidateMethods[i]) && !candidateMethods[i].getName().equals("clone")) { //$NON-NLS-1$ //$NON-NLS-2$
methodsToBindings.put(candidateMethods[i].getJavaElement(), candidateMethods[i]);
}
}
populateMembers(fInheritedMethods, type.getMethods(), methodsToBindings);
}
return true;
}
/**
* Populates <code>result</code> with the bindings from <code>membersToBindings</code>, sorted
* in the order of <code>allMembers</code>.
*
* @param result list of bindings from membersToBindings, sorted in source order
* @param allMembers all member elements in source order
* @param membersToBindings map from {@link IMember} to {@link IBinding}
* @since 3.6
*/
private static <T extends IBinding> void populateMembers(List<T> result, IMember[] allMembers, HashMap<IJavaElement, T> membersToBindings) {
for (int i= 0; i < allMembers.length; i++) {
T memberBinding= membersToBindings.remove(allMembers[i]);
if (memberBinding != null) {
result.add(memberBinding);
}
}
}
private static <T extends IBinding> boolean contains(List<T> inheritedFields, T member) {
for (Iterator<T> iterator= inheritedFields.iterator(); iterator.hasNext();) {
T object= iterator.next();
if (object instanceof IVariableBinding && member instanceof IVariableBinding)
if (((IVariableBinding) object).getName().equals(((IVariableBinding) member).getName()))
return true;
if (object instanceof IMethodBinding && member instanceof IMethodBinding)
if (((IMethodBinding) object).getName().equals(((IMethodBinding) member).getName()))
return true;
}
return false;
}
@Override
String getAlreadyImplementedErrorMethodName() {
return ActionMessages.GenerateToStringAction_tostring;
}
@Override
boolean isMethodAlreadyImplemented(ITypeBinding typeBinding) {
return new ToStringInfo(typeBinding).foundToString;
}
@Override
String getErrorCaption() {
return ActionMessages.GenerateToStringAction_error_caption;
}
@Override
String getNoMembersError() {
//no members error never occurs
return null;
}
}