| /******************************************************************************* |
| * Copyright (c) 2007, 2011 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 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.ui.text.correction; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.widgets.Display; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| |
| import org.eclipse.core.resources.IFile; |
| |
| import org.eclipse.jface.text.IDocument; |
| |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PlatformUI; |
| |
| import org.eclipse.ltk.core.refactoring.Change; |
| import org.eclipse.ltk.core.refactoring.CompositeChange; |
| import org.eclipse.ltk.core.refactoring.RefactoringStatus; |
| import org.eclipse.ltk.core.refactoring.TextFileChange; |
| |
| import org.eclipse.jdt.core.Flags; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IField; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jdt.core.dom.AST; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.Expression; |
| 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.MethodInvocation; |
| import org.eclipse.jdt.core.dom.Modifier; |
| import org.eclipse.jdt.core.dom.Name; |
| import org.eclipse.jdt.core.dom.QualifiedName; |
| import org.eclipse.jdt.core.dom.SimpleName; |
| import org.eclipse.jdt.core.dom.SuperFieldAccess; |
| import org.eclipse.jdt.core.dom.SuperMethodInvocation; |
| import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; |
| |
| import org.eclipse.jdt.internal.corext.codemanipulation.GetterSetterUtil; |
| import org.eclipse.jdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.jdt.internal.corext.dom.Bindings; |
| import org.eclipse.jdt.internal.corext.refactoring.RefactoringAvailabilityTester; |
| import org.eclipse.jdt.internal.corext.refactoring.sef.SelfEncapsulateFieldRefactoring; |
| import org.eclipse.jdt.internal.corext.util.JavaModelUtil; |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| |
| import org.eclipse.jdt.ui.refactoring.RefactoringSaveHelper; |
| import org.eclipse.jdt.ui.text.java.IInvocationContext; |
| import org.eclipse.jdt.ui.text.java.IProblemLocation; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.ui.JavaPluginImages; |
| import org.eclipse.jdt.internal.ui.refactoring.RefactoringExecutionHelper; |
| import org.eclipse.jdt.internal.ui.refactoring.actions.RefactoringStarter; |
| import org.eclipse.jdt.internal.ui.refactoring.sef.SelfEncapsulateFieldWizard; |
| import org.eclipse.jdt.internal.ui.text.correction.proposals.ASTRewriteCorrectionProposal; |
| import org.eclipse.jdt.internal.ui.text.correction.proposals.ChangeCorrectionProposal; |
| import org.eclipse.jdt.internal.ui.util.ExceptionHandler; |
| import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; |
| |
| |
| public class GetterSetterCorrectionSubProcessor { |
| |
| public static final String SELF_ENCAPSULATE_FIELD_ID= "org.eclipse.jdt.ui.correction.encapsulateField.assist"; //$NON-NLS-1$ |
| |
| private static class ProposalParameter { |
| public final boolean useSuper; |
| public final ICompilationUnit compilationUnit; |
| public final ASTRewrite astRewrite; |
| public final Expression accessNode; |
| public final Expression qualifier; |
| public final IVariableBinding variableBinding; |
| |
| public ProposalParameter(boolean useSuper, ICompilationUnit compilationUnit, ASTRewrite rewrite, Expression accessNode, Expression qualifier, IVariableBinding variableBinding) { |
| this.useSuper= useSuper; |
| this.compilationUnit= compilationUnit; |
| this.astRewrite= rewrite; |
| this.accessNode= accessNode; |
| this.qualifier= qualifier; |
| this.variableBinding= variableBinding; |
| } |
| } |
| |
| public static class SelfEncapsulateFieldProposal extends ChangeCorrectionProposal { // public for tests |
| |
| private IField fField; |
| private boolean fNoDialog; |
| |
| public SelfEncapsulateFieldProposal(int relevance, IField field) { |
| super(getDescription(field), null, relevance, JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE)); |
| fField= field; |
| fNoDialog= false; |
| setCommandId(SELF_ENCAPSULATE_FIELD_ID); |
| } |
| |
| public IField getField() { |
| return fField; |
| } |
| |
| public void setNoDialog(boolean noDialog) { |
| fNoDialog= noDialog; |
| } |
| |
| public TextFileChange getChange(IFile file) throws CoreException { |
| final SelfEncapsulateFieldRefactoring refactoring= new SelfEncapsulateFieldRefactoring(fField); |
| refactoring.setVisibility(Flags.AccPublic); |
| refactoring.setConsiderVisibility(false);//private field references are just searched in local file |
| refactoring.checkInitialConditions(new NullProgressMonitor()); |
| refactoring.checkFinalConditions(new NullProgressMonitor()); |
| Change createdChange= refactoring.createChange(new NullProgressMonitor()); |
| if (createdChange instanceof CompositeChange) { |
| Change[] children= ((CompositeChange) createdChange).getChildren(); |
| for (int i= 0; i < children.length; i++) { |
| Change curr= children[i]; |
| if (curr instanceof TextFileChange && ((TextFileChange) curr).getFile().equals(file)) { |
| return (TextFileChange) curr; |
| } |
| } |
| } |
| return null; |
| } |
| |
| |
| private static String getDescription(IField field) { |
| return Messages.format(CorrectionMessages.GetterSetterCorrectionSubProcessor_creategetterunsingencapsulatefield_description, BasicElementLabels.getJavaElementName(field.getElementName())); |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension5#getAdditionalProposalInfo(org.eclipse.core.runtime.IProgressMonitor) |
| * @since 3.5 |
| */ |
| @Override |
| public Object getAdditionalProposalInfo(IProgressMonitor monitor) { |
| return CorrectionMessages.GetterSetterCorrectionSubProcessor_additional_info; |
| } |
| |
| @Override |
| public void apply(IDocument document) { |
| try { |
| final SelfEncapsulateFieldRefactoring refactoring= new SelfEncapsulateFieldRefactoring(fField); |
| refactoring.setVisibility(Flags.AccPublic); |
| refactoring.setConsiderVisibility(false);//private field references are just searched in local file |
| if (fNoDialog) { |
| IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow(); |
| final RefactoringExecutionHelper helper= new RefactoringExecutionHelper(refactoring, RefactoringStatus.ERROR, RefactoringSaveHelper.SAVE_REFACTORING, JavaPlugin.getActiveWorkbenchShell(), window); |
| if (Display.getCurrent() != null) { |
| try { |
| helper.perform(false, false); |
| } catch (InterruptedException e) { |
| JavaPlugin.log(e); |
| } catch (InvocationTargetException e) { |
| JavaPlugin.log(e); |
| } |
| } else { |
| Display.getDefault().syncExec(new Runnable() { |
| public void run() { |
| try { |
| helper.perform(false, false); |
| } catch (InterruptedException e) { |
| JavaPlugin.log(e); |
| } catch (InvocationTargetException e) { |
| JavaPlugin.log(e); |
| } |
| } |
| }); |
| } |
| } else { |
| new RefactoringStarter().activate(new SelfEncapsulateFieldWizard(refactoring), JavaPlugin.getActiveWorkbenchShell(), "", RefactoringSaveHelper.SAVE_REFACTORING); //$NON-NLS-1$ |
| } |
| } catch (JavaModelException e) { |
| ExceptionHandler.handle(e, CorrectionMessages.GetterSetterCorrectionSubProcessor_encapsulate_field_error_title, CorrectionMessages.GetterSetterCorrectionSubProcessor_encapsulate_field_error_message); |
| } |
| } |
| } |
| |
| /** |
| * Used by quick assist |
| * @param context the invocation context |
| * @param coveringNode the covering node |
| * @param locations the problems at the corrent location |
| * @param resultingCollections the resulting proposals |
| * @return <code>true</code> if the quick assist is applicable at this offset |
| */ |
| public static boolean addGetterSetterProposal(IInvocationContext context, ASTNode coveringNode, IProblemLocation[] locations, ArrayList<ICommandAccess> resultingCollections) { |
| if (locations != null) { |
| for (int i= 0; i < locations.length; i++) { |
| int problemId= locations[i].getProblemId(); |
| if (problemId == IProblem.UnusedPrivateField) |
| return false; |
| if (problemId == IProblem.UnqualifiedFieldAccess) |
| return false; |
| } |
| } |
| return addGetterSetterProposal(context, coveringNode, resultingCollections, 7); |
| } |
| |
| public static void addGetterSetterProposal(IInvocationContext context, IProblemLocation location, Collection<ICommandAccess> proposals, int relevance) { |
| addGetterSetterProposal(context, location.getCoveringNode(context.getASTRoot()), proposals, relevance); |
| } |
| |
| private static boolean addGetterSetterProposal(IInvocationContext context, ASTNode coveringNode, Collection<ICommandAccess> proposals, int relevance) { |
| if (!(coveringNode instanceof SimpleName)) { |
| return false; |
| } |
| SimpleName sn= (SimpleName) coveringNode; |
| |
| IBinding binding= sn.resolveBinding(); |
| if (!(binding instanceof IVariableBinding)) |
| return false; |
| IVariableBinding variableBinding= (IVariableBinding) binding; |
| if (!variableBinding.isField()) |
| return false; |
| |
| if (proposals == null) |
| return true; |
| |
| ChangeCorrectionProposal proposal= getProposal(context.getCompilationUnit(), sn, variableBinding, relevance); |
| if (proposal != null) |
| proposals.add(proposal); |
| return true; |
| } |
| |
| private static ChangeCorrectionProposal getProposal(ICompilationUnit cu, SimpleName sn, IVariableBinding variableBinding, int relevance) { |
| Expression accessNode= sn; |
| Expression qualifier= null; |
| boolean useSuper= false; |
| |
| ASTNode parent= sn.getParent(); |
| switch (parent.getNodeType()) { |
| case ASTNode.QUALIFIED_NAME: |
| accessNode= (Expression) parent; |
| qualifier= ((QualifiedName) parent).getQualifier(); |
| break; |
| case ASTNode.SUPER_FIELD_ACCESS: |
| accessNode= (Expression) parent; |
| qualifier= ((SuperFieldAccess) parent).getQualifier(); |
| useSuper= true; |
| break; |
| } |
| ASTRewrite rewrite= ASTRewrite.create(sn.getAST()); |
| ProposalParameter gspc= new ProposalParameter(useSuper, cu, rewrite, accessNode, qualifier, variableBinding); |
| if (ASTResolving.isWriteAccess(sn)) |
| return addSetterProposal(gspc, relevance); |
| else |
| return addGetterProposal(gspc, relevance); |
| } |
| |
| /** |
| * Proposes a getter for this field. |
| * |
| * @param context the proposal parameter |
| * @param relevance relevance of this proposal |
| * @return the proposal if available or null |
| */ |
| private static ChangeCorrectionProposal addGetterProposal(ProposalParameter context, int relevance) { |
| IMethodBinding method= findGetter(context); |
| if (method != null) { |
| Expression mi= createMethodInvocation(context, method, null); |
| context.astRewrite.replace(context.accessNode, mi, null); |
| |
| String label= Messages.format(CorrectionMessages.GetterSetterCorrectionSubProcessor_replacewithgetter_description, BasicElementLabels.getJavaCodeString(ASTNodes.asString(context.accessNode))); |
| Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE); |
| ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.compilationUnit, context.astRewrite, relevance, image); |
| return proposal; |
| } else { |
| IJavaElement element= context.variableBinding.getJavaElement(); |
| if (element instanceof IField) { |
| IField field= (IField) element; |
| try { |
| if (RefactoringAvailabilityTester.isSelfEncapsulateAvailable(field)) |
| return new SelfEncapsulateFieldProposal(relevance, field); |
| } catch (JavaModelException e) { |
| JavaPlugin.log(e); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static IMethodBinding findGetter(ProposalParameter context) { |
| ITypeBinding returnType= context.variableBinding.getType(); |
| String getterName= GetterSetterUtil.getGetterName(context.variableBinding, context.compilationUnit.getJavaProject(), null, isBoolean(context)); |
| ITypeBinding declaringType= context.variableBinding.getDeclaringClass(); |
| if (declaringType == null) |
| return null; |
| IMethodBinding getter= Bindings.findMethodInHierarchy(declaringType, getterName, new ITypeBinding[0]); |
| if (getter != null && getter.getReturnType().isAssignmentCompatible(returnType) && Modifier.isStatic(getter.getModifiers()) == Modifier.isStatic(context.variableBinding.getModifiers())) |
| return getter; |
| return null; |
| } |
| |
| private static Expression createMethodInvocation(ProposalParameter context, IMethodBinding method, Expression argument) { |
| AST ast= context.astRewrite.getAST(); |
| Expression qualifier= context.qualifier; |
| if (context.useSuper) { |
| SuperMethodInvocation invocation= ast.newSuperMethodInvocation(); |
| invocation.setName(ast.newSimpleName(method.getName())); |
| if (qualifier != null) |
| invocation.setQualifier((Name) context.astRewrite.createCopyTarget(qualifier)); |
| if (argument != null) |
| invocation.arguments().add(argument); |
| return invocation; |
| } else { |
| MethodInvocation invocation= ast.newMethodInvocation(); |
| invocation.setName(ast.newSimpleName(method.getName())); |
| if (qualifier != null) |
| invocation.setExpression((Expression) context.astRewrite.createCopyTarget(qualifier)); |
| if (argument != null) |
| invocation.arguments().add(argument); |
| return invocation; |
| } |
| } |
| |
| /** |
| * Proposes a setter for this field. |
| * |
| * @param context the proposal parameter |
| * @param relevance relevance of this proposal |
| * @return the proposal if available or null |
| */ |
| private static ChangeCorrectionProposal addSetterProposal(ProposalParameter context, int relevance) { |
| boolean isBoolean= isBoolean(context); |
| String setterName= GetterSetterUtil.getSetterName(context.variableBinding, context.compilationUnit.getJavaProject(), null, isBoolean); |
| ITypeBinding declaringType= context.variableBinding.getDeclaringClass(); |
| if (declaringType == null) |
| return null; |
| |
| IMethodBinding method= Bindings.findMethodInHierarchy(declaringType, setterName, new ITypeBinding[] { context.variableBinding.getType() }); |
| if (method != null && Bindings.isVoidType(method.getReturnType()) && (Modifier.isStatic(method.getModifiers()) == Modifier.isStatic(context.variableBinding.getModifiers()))) { |
| Expression assignedValue= getAssignedValue(context); |
| if (assignedValue == null) |
| return null; //we don't know how to handle those cases. |
| Expression mi= createMethodInvocation(context, method, assignedValue); |
| context.astRewrite.replace(context.accessNode.getParent(), mi, null); |
| |
| String label= Messages.format(CorrectionMessages.GetterSetterCorrectionSubProcessor_replacewithsetter_description, BasicElementLabels.getJavaCodeString(ASTNodes.asString(context.accessNode))); |
| Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE); |
| ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.compilationUnit, context.astRewrite, relevance, image); |
| return proposal; |
| } else { |
| IJavaElement element= context.variableBinding.getJavaElement(); |
| if (element instanceof IField) { |
| IField field= (IField) element; |
| try { |
| if (RefactoringAvailabilityTester.isSelfEncapsulateAvailable(field)) |
| return new SelfEncapsulateFieldProposal(relevance, field); |
| } catch (JavaModelException e) { |
| JavaPlugin.log(e); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static boolean isBoolean(ProposalParameter context) { |
| AST ast= context.astRewrite.getAST(); |
| boolean isBoolean= ast.resolveWellKnownType("boolean") == context.variableBinding.getType(); //$NON-NLS-1$ |
| if (!isBoolean) |
| isBoolean= ast.resolveWellKnownType("java.lang.Boolean") == context.variableBinding.getType(); //$NON-NLS-1$ |
| return isBoolean; |
| } |
| |
| private static Expression getAssignedValue(ProposalParameter context) { |
| ASTNode parent= context.accessNode.getParent(); |
| ASTRewrite astRewrite= context.astRewrite; |
| IJavaProject javaProject= context.compilationUnit.getJavaProject(); |
| IMethodBinding getter= findGetter(context); |
| Expression getterExpression= null; |
| if (getter != null) { |
| getterExpression= astRewrite.getAST().newSimpleName("placeholder"); //$NON-NLS-1$ |
| } |
| ITypeBinding type= context.variableBinding.getType(); |
| boolean is50OrHigher= JavaModelUtil.is50OrHigher(javaProject); |
| Expression result= GetterSetterUtil.getAssignedValue(parent, astRewrite, getterExpression, type, is50OrHigher); |
| if (result != null && getterExpression != null && getterExpression.getParent() != null) { |
| getterExpression.getParent().setStructuralProperty(getterExpression.getLocationInParent(), createMethodInvocation(context, getter, null)); |
| } |
| return result; |
| } |
| |
| } |