blob: 3ac6dc63fbf4edea618b71d12c446825fbbb19e3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Dmitry Stalnov (dstalnov@fusionone.com) - contributed fixes for:
* o Allow 'this' constructor to be inlined
* (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38093)
*******************************************************************************/
package org.eclipse.dltk.internal.javascript.corext.refactoring.code;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.SourceRange;
import org.eclipse.dltk.core.search.IDLTKSearchConstants;
import org.eclipse.dltk.core.search.IDLTKSearchScope;
import org.eclipse.dltk.core.search.SearchEngine;
import org.eclipse.dltk.core.search.SearchMatch;
import org.eclipse.dltk.core.search.SearchPattern;
import org.eclipse.dltk.internal.corext.refactoring.CollectingSearchRequestor;
import org.eclipse.dltk.internal.corext.refactoring.RefactoringScopeFactory;
import org.eclipse.dltk.internal.corext.refactoring.base.ScriptStatusContext;
import org.eclipse.dltk.internal.corext.util.SearchUtils;
import org.eclipse.dltk.internal.javascript.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.dltk.javascript.core.JavaScriptLanguageToolkit;
import org.eclipse.dltk.javascript.core.dom.CallExpression;
import org.eclipse.dltk.javascript.core.dom.Node;
import org.eclipse.dltk.javascript.core.dom.Source;
import org.eclipse.dltk.javascript.core.dom.rewrite.ASTConverter;
import org.eclipse.dltk.javascript.core.dom.rewrite.NodeFinder;
import org.eclipse.dltk.javascript.core.dom.rewrite.RefactoringUtils;
import org.eclipse.dltk.javascript.parser.JavaScriptParserUtil;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
/**
* A TargetProvider provides all targets that have to be adapted, i.e. all method invocations that should be inlined.
*/
abstract class TargetProvider {
//public abstract void initialize();
public abstract List<ISourceModule> getAffectedSourceModules(RefactoringStatus status, IProgressMonitor pm) throws CoreException;
public abstract Source getRoot(ISourceModule unit);
public abstract Node[] getAffectedBodyDeclarations(ISourceModule unit, IProgressMonitor pm);
// constructor invocation is not an expression but a statement
public abstract CallExpression[] getInvocations(Node declaration, IProgressMonitor pm);
public abstract int getStatusSeverity();
/*public boolean isSingle() {
return false;
}*/
public static TargetProvider create(IMethod method) {
return new MemberTypeTargetProvider(method);
}
public static TargetProvider create(ISourceModule cu, int offset, int length) {
return new SingleCallTargetProvider(cu, offset, length);
}
/*public static TargetProvider create(MethodDeclaration declaration) {
IMethodBinding method= declaration.resolveBinding();
if (method == null)
return new ErrorTargetProvider(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.TargetProvider_method_declaration_not_unique));
ITypeBinding type= method.getDeclaringClass();
if (type.isLocal()) {
if (((IType) type.getJavaElement()).isBinary()) {
return new ErrorTargetProvider(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.TargetProvider_cannot_local_method_in_binary));
} else {
IType declaringClassOfLocal= (IType) type.getDeclaringClass().getJavaElement();
return new LocalTypeTargetProvider(declaringClassOfLocal.getCompilationUnit(), declaration);
}
} else {
return new MemberTypeTargetProvider(declaration.resolveBinding());
}
}*/
static void fastDone(IProgressMonitor pm) {
if (pm == null)
return;
pm.beginTask("", 1); //$NON-NLS-1$
pm.worked(1);
pm.done();
}
/*static class ErrorTargetProvider extends TargetProvider {
private RefactoringStatus fErrorStatus;
public ErrorTargetProvider(RefactoringStatus status) {
fErrorStatus= status;
}
public RefactoringStatus checkActivation() throws JavaModelException {
return fErrorStatus;
}
public void initialize() {
}
public ICompilationUnit[] getAffectedCompilationUnits(RefactoringStatus status, ReferencesInBinaryContext binaryRefs, IProgressMonitor pm) throws JavaModelException {
return null;
}
public BodyDeclaration[] getAffectedBodyDeclarations(ICompilationUnit unit, IProgressMonitor pm) {
return null;
}
public ASTNode[] getInvocations(BodyDeclaration declaration, IProgressMonitor pm) {
return null;
}
public int getStatusSeverity() {
return 0;
}
}*/
static class SingleCallTargetProvider extends TargetProvider {
private ISourceModule cu;
private Source root;
private CallExpression invocation;
//private boolean fIterated;
public SingleCallTargetProvider(ISourceModule cu, int offset, int length) {
this.cu = cu;
root = (Source)ASTConverter.convert(JavaScriptParserUtil.parse(cu));
invocation = RefactoringUtils.getFunctionReference(NodeFinder.findNode(root, offset, offset+length));
}
/*public void initialize() {
fIterated= false;
}*/
@Override
public List<ISourceModule> getAffectedSourceModules(RefactoringStatus status, IProgressMonitor pm) {
List<ISourceModule> res= new ArrayList<ISourceModule>(1);
res.add(cu);
return res;
}
@Override
public Source getRoot(ISourceModule unit) {
Assert.isTrue(unit == cu);
return root;
}
@Override
public Node[] getAffectedBodyDeclarations(ISourceModule unit, IProgressMonitor pm) {
Assert.isTrue(unit == cu);
//if (fIterated)
// return new BodyDeclaration[0];
fastDone(pm);
return new Node[] { NodeFinder.findEnclosingNode(invocation) };
}
@Override
public CallExpression[] getInvocations(Node declaration, IProgressMonitor pm) {
fastDone(pm);
//if (fIterated)
// return null;
//fIterated= true;
return new CallExpression[] { invocation };
}
@Override
public int getStatusSeverity() {
return RefactoringStatus.FATAL;
}
/*public boolean isSingle() {
return true;
}*/
}
/*private static class BodyData {
private List<Identifier> fInvocations;
public BodyData() {
}
public void addInvocation(Identifier node) {
if (fInvocations == null)
fInvocations= new ArrayList(2);
fInvocations.add(node);
}
public Node[] getInvocations() {
return (Node[])fInvocations.toArray(new ASTNode[fInvocations.size()]);
}
public boolean hasInvocations() {
return fInvocations != null && !fInvocations.isEmpty();
}
}
private static class InvocationFinder extends ASTVisitor {
Map/*<BodyDeclaration, BodyData>* result= new HashMap(2);
Stack/*<BodyData>* fBodies= new Stack();
BodyData fCurrent;
private IMethodBinding fBinding;
public InvocationFinder(IMethodBinding binding) {
Assert.isNotNull(binding);
fBinding= binding.getMethodDeclaration();
Assert.isNotNull(fBinding);
}
public boolean visit(MethodInvocation node) {
if (matches(node.getName().resolveBinding()) && fCurrent != null) {
fCurrent.addInvocation(node);
}
return true;
}
public boolean visit(SuperMethodInvocation node) {
if (matches(node.getName().resolveBinding()) && fCurrent != null) {
fCurrent.addInvocation(node);
}
return true;
}
public boolean visit(ConstructorInvocation node) {
if (matches(node.resolveConstructorBinding()) && fCurrent != null) {
fCurrent.addInvocation(node);
}
return true;
}
public boolean visit(ClassInstanceCreation node) {
if (matches(node.resolveConstructorBinding()) && fCurrent != null) {
fCurrent.addInvocation(node);
}
return true;
}
public boolean visit(TypeDeclaration node) {
return visitType();
}
public void endVisit(TypeDeclaration node) {
endVisitBodyDeclaration();
}
public boolean visit(EnumDeclaration node) {
return visitType();
}
public void endVisit(EnumDeclaration node) {
endVisitBodyDeclaration();
}
public boolean visit(AnnotationTypeDeclaration node) {
return visitType();
}
public void endVisit(AnnotationTypeDeclaration node) {
endVisitBodyDeclaration();
}
private boolean visitType() {
fBodies.add(fCurrent);
fCurrent= null;
return true;
}
protected boolean visitNonTypeBodyDeclaration() {
fBodies.add(fCurrent);
fCurrent= new BodyData();
return true;
}
protected void endVisitBodyDeclaration() {
fCurrent= (BodyData)fBodies.remove(fBodies.size() - 1);
}
public boolean visit(FieldDeclaration node) {
return visitNonTypeBodyDeclaration();
}
public void endVisit(FieldDeclaration node) {
if (fCurrent.hasInvocations()) {
result.put(node, fCurrent);
}
endVisitBodyDeclaration();
}
public boolean visit(MethodDeclaration node) {
return visitNonTypeBodyDeclaration();
}
public void endVisit(MethodDeclaration node) {
if (fCurrent.hasInvocations()) {
result.put(node, fCurrent);
}
endVisitBodyDeclaration();
}
public boolean visit(Initializer node) {
return visitNonTypeBodyDeclaration();
}
public void endVisit(Initializer node) {
if (fCurrent.hasInvocations()) {
result.put(node, fCurrent);
}
endVisitBodyDeclaration();
}
private boolean matches(IBinding binding) {
if (!(binding instanceof IMethodBinding))
return false;
return fBinding.isEqualTo(((IMethodBinding)binding).getMethodDeclaration());
}
}
private static class LocalTypeTargetProvider extends TargetProvider {
private ICompilationUnit fCUnit;
private MethodDeclaration fDeclaration;
private Map fBodies;
public LocalTypeTargetProvider(ICompilationUnit unit, MethodDeclaration declaration) {
Assert.isNotNull(unit);
Assert.isNotNull(declaration);
fCUnit= unit;
fDeclaration= declaration;
}
public void initialize() {
IMethodBinding methodBinding= fDeclaration.resolveBinding();
InvocationFinder finder;
ASTNode type= ASTNodes.getParent(fDeclaration, AbstractTypeDeclaration.class);
if (methodBinding.getDeclaringClass().isAnonymous()) {
finder= new InvocationFinder(methodBinding);
type.accept(finder);
} else {
//scope of local class is enclosing block
ASTNode block= type.getParent().getParent();
finder= new InvocationFinder(methodBinding) {
public boolean visit(Block node) {
return visitNonTypeBodyDeclaration();
}
public void endVisit(Block node) {
if (fCurrent.hasInvocations()) {
result.put(ASTNodes.getParent(node, BodyDeclaration.class), fCurrent);
}
endVisitBodyDeclaration();
}
};
block.accept(finder);
}
fBodies= finder.result;
}
public ICompilationUnit[] getAffectedCompilationUnits(RefactoringStatus status, ReferencesInBinaryContext binaryRefs, IProgressMonitor pm) {
fastDone(pm);
return new ICompilationUnit[] { fCUnit };
}
public BodyDeclaration[] getAffectedBodyDeclarations(ICompilationUnit unit, IProgressMonitor pm) {
Assert.isTrue(unit == fCUnit);
Set result= fBodies.keySet();
fastDone(pm);
return (BodyDeclaration[])result.toArray(new BodyDeclaration[result.size()]);
}
public ASTNode[] getInvocations(BodyDeclaration declaration, IProgressMonitor pm) {
BodyData data= (BodyData)fBodies.get(declaration);
Assert.isNotNull(data);
fastDone(pm);
return data.getInvocations();
}
public RefactoringStatus checkActivation() throws JavaModelException {
return new RefactoringStatus();
}
public int getStatusSeverity() {
return RefactoringStatus.ERROR;
}
}*/
private static class MemberTypeTargetProvider extends TargetProvider {
private final IMethod method;
private final Map<ISourceModule,Source> roots = new HashMap<ISourceModule,Source>();
private Map<ISourceModule,List<SearchMatch>> calls;
private Map<Node,List<CallExpression>> currentBodies;
public MemberTypeTargetProvider(IMethod method) {
Assert.isNotNull(method);
this.method = method;
}
@Override
public List<ISourceModule> getAffectedSourceModules(final RefactoringStatus status, IProgressMonitor pm) throws CoreException {
SearchPattern pattern= SearchPattern.createPattern(method, IDLTKSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE, JavaScriptLanguageToolkit.getDefault());
IDLTKSearchScope scope= RefactoringScopeFactory.create(method, true, false);
//SearchPattern pattern= SearchPattern.createPattern(method.getElementName(), IDLTKSearchConstants.METHOD, IDLTKSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE, JavaScriptLanguageToolkit.getDefault());
//IDLTKSearchScope scope = SearchEngine.createWorkspaceScope(JavaScriptLanguageToolkit.getDefault());
if (calls == null) {
calls = new HashMap<ISourceModule,List<SearchMatch>>();
CollectingSearchRequestor requestor= new CollectingSearchRequestor() {
public void acceptSearchMatch(SearchMatch match) throws CoreException {
if (filterMatch(match))
return;
if (match.isInsideDocComment())
return; // TODO: should warn user
ISourceModule unit= SearchUtils.getSourceModule(match);
if (match.getAccuracy() == SearchMatch.A_INACCURATE) {
if (unit != null) {
status.addError(RefactoringCoreMessages.TargetProvider_inaccurate_match,
ScriptStatusContext.create(unit, new SourceRange(match.getOffset(), match.getLength())));
} else {
status.addError(RefactoringCoreMessages.TargetProvider_inaccurate_match);
}
} else if (unit != null) {
List<SearchMatch> list = calls.get(unit);
if (list == null) {
list = new ArrayList<SearchMatch>();
calls.put(unit,list);
}
list.add(match);
}
}
};
new SearchEngine().search(pattern, SearchUtils.getDefaultSearchParticipants(), scope, requestor, new SubProgressMonitor(pm, 1));
}
return new ArrayList<ISourceModule>(calls.keySet());
}
@Override
public Source getRoot(ISourceModule unit) {
Source root = roots.get(unit);
if (root == null) {
root = (Source)ASTConverter.convert(JavaScriptParserUtil.parse(unit));
roots.put(unit,root);
}
return root;
}
@Override
public Node[] getAffectedBodyDeclarations(ISourceModule unit, IProgressMonitor pm) {
Source root=roots.get(unit);
currentBodies = new HashMap<Node,List<CallExpression>>();
for(Node node : NodeFinder.findNodes(root, calls.get(unit))) {
CallExpression expr = RefactoringUtils.getFunctionReference(node);
// TODO something with bad references
if (expr == null)
continue;
Node enclosing = NodeFinder.findEnclosingNode(expr);
List<CallExpression> list = currentBodies.get(enclosing);
if (list == null) {
list = new ArrayList<CallExpression>();
currentBodies.put(enclosing,list);
}
list.add(expr);
}
Set<Node> result= currentBodies.keySet();
fastDone(pm);
return (Node[])result.toArray(new Node[result.size()]);
}
@Override
public CallExpression[] getInvocations(Node declaration, IProgressMonitor pm) {
List<CallExpression> list = currentBodies.get(declaration);
return list.toArray(new CallExpression[list.size()]);
}
@Override
public int getStatusSeverity() {
return RefactoringStatus.ERROR;
}
}
}