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