/******************************************************************************* * 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; } }