/******************************************************************************* * Copyright (c) 2000, 2008 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 * 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.wst.jsdt.internal.corext.refactoring.code; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.wst.jsdt.core.IFunction; import org.eclipse.wst.jsdt.core.IJavaScriptElement; import org.eclipse.wst.jsdt.core.IJavaScriptUnit; import org.eclipse.wst.jsdt.core.IType; import org.eclipse.wst.jsdt.core.JavaScriptModelException; import org.eclipse.wst.jsdt.core.dom.AST; import org.eclipse.wst.jsdt.core.dom.ASTNode; import org.eclipse.wst.jsdt.core.dom.ASTVisitor; import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration; import org.eclipse.wst.jsdt.core.dom.BodyDeclaration; import org.eclipse.wst.jsdt.core.dom.ClassInstanceCreation; import org.eclipse.wst.jsdt.core.dom.ConstructorInvocation; import org.eclipse.wst.jsdt.core.dom.FieldDeclaration; import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration; import org.eclipse.wst.jsdt.core.dom.FunctionInvocation; import org.eclipse.wst.jsdt.core.dom.IBinding; import org.eclipse.wst.jsdt.core.dom.IFunctionBinding; import org.eclipse.wst.jsdt.core.dom.ITypeBinding; import org.eclipse.wst.jsdt.core.dom.Initializer; import org.eclipse.wst.jsdt.core.dom.SuperMethodInvocation; import org.eclipse.wst.jsdt.core.dom.TypeDeclaration; import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchConstants; import org.eclipse.wst.jsdt.core.search.SearchMatch; import org.eclipse.wst.jsdt.core.search.SearchPattern; import org.eclipse.wst.jsdt.internal.corext.SourceRange; import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes; import org.eclipse.wst.jsdt.internal.corext.refactoring.IRefactoringSearchRequestor; import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringScopeFactory; import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringSearchEngine2; import org.eclipse.wst.jsdt.internal.corext.refactoring.base.JavaStatusContext; import org.eclipse.wst.jsdt.internal.corext.refactoring.util.RefactoringASTParser; import org.eclipse.wst.jsdt.internal.corext.util.SearchUtils; /** * A TargetProvider provides all targets that have to be adapted, i.e. all method invocations that should be inlined. */ abstract class TargetProvider { public static final boolean BUG_CORE_130317= true; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=130317 protected SourceProvider fSourceProvider; //TODO: not used... public void setSourceProvider(SourceProvider sourceProvider) { Assert.isNotNull(sourceProvider); fSourceProvider= sourceProvider; } public abstract void initialize(); public abstract IJavaScriptUnit[] getAffectedCompilationUnits(RefactoringStatus status, IProgressMonitor pm) throws JavaScriptModelException; public abstract BodyDeclaration[] getAffectedBodyDeclarations(IJavaScriptUnit unit, IProgressMonitor pm); // constructor invocation is not an expression but a statement public abstract ASTNode[] getInvocations(BodyDeclaration declaration, IProgressMonitor pm); public abstract RefactoringStatus checkActivation() throws JavaScriptModelException; public abstract int getStatusSeverity(); public boolean isSingle() { return false; } public static TargetProvider create(IJavaScriptUnit cu, FunctionInvocation invocation) { return new SingleCallTargetProvider(cu, invocation); } public static TargetProvider create(IJavaScriptUnit cu, SuperMethodInvocation invocation) { return new SingleCallTargetProvider(cu, invocation); } public static TargetProvider create(IJavaScriptUnit cu, ConstructorInvocation invocation) { return new SingleCallTargetProvider(cu, invocation); } public static TargetProvider create(FunctionDeclaration declaration) { IFunctionBinding 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.getJavaScriptUnit(), declaration); } } else { return new MemberTypeTargetProvider(declaration.resolveBinding()); } } public static TargetProvider create(IFunctionBinding methodBinding) { return new MemberTypeTargetProvider(methodBinding); } 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 JavaScriptModelException { return fErrorStatus; } public void initialize() { } public IJavaScriptUnit[] getAffectedCompilationUnits(RefactoringStatus status, IProgressMonitor pm) throws JavaScriptModelException { return null; } public BodyDeclaration[] getAffectedBodyDeclarations(IJavaScriptUnit 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 IJavaScriptUnit fCUnit; private ASTNode fInvocation; private boolean fIterated; public SingleCallTargetProvider(IJavaScriptUnit cu, ASTNode invocation) { Assert.isNotNull(cu); Assert.isNotNull(invocation); Assert.isTrue(Invocations.isInvocation(invocation)); fCUnit= cu; fInvocation= invocation; } public void initialize() { fIterated= false; } public IJavaScriptUnit[] getAffectedCompilationUnits(RefactoringStatus status, IProgressMonitor pm) { return new IJavaScriptUnit[] { fCUnit }; } public BodyDeclaration[] getAffectedBodyDeclarations(IJavaScriptUnit unit, IProgressMonitor pm) { Assert.isTrue(unit == fCUnit); if (fIterated) return new BodyDeclaration[0]; fastDone(pm); return new BodyDeclaration[] { (BodyDeclaration)ASTNodes.getParent(fInvocation, BodyDeclaration.class) }; } public ASTNode[] getInvocations(BodyDeclaration declaration, IProgressMonitor pm) { fastDone(pm); if (fIterated) return null; fIterated= true; return new ASTNode[] { fInvocation }; } public RefactoringStatus checkActivation() throws JavaScriptModelException { return new RefactoringStatus(); } public int getStatusSeverity() { return RefactoringStatus.FATAL; } public boolean isSingle() { return true; } } private static class BodyData { public BodyDeclaration fBody; private List fInvocations; public BodyData(BodyDeclaration declaration) { fBody= declaration; } public void addInvocation(ASTNode node) { if (fInvocations == null) fInvocations= new ArrayList(2); fInvocations.add(node); } public ASTNode[] getInvocations() { return (ASTNode[])fInvocations.toArray(new ASTNode[fInvocations.size()]); } public boolean hasInvocations() { return fInvocations != null && !fInvocations.isEmpty(); } public BodyDeclaration getDeclaration() { return fBody; } } private static class InvocationFinder extends ASTVisitor { Map/*<BodyDeclaration, BodyData>*/ result= new HashMap(2); Stack/*<BodyData>*/ fBodies= new Stack(); BodyData fCurrent; private IFunctionBinding fBinding; public InvocationFinder(IFunctionBinding binding) { Assert.isNotNull(binding); fBinding= binding.getMethodDeclaration(); Assert.isNotNull(fBinding); } public boolean visit(FunctionInvocation 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) { endVisitType(); } private boolean visitType() { fBodies.add(fCurrent); fCurrent= null; return true; } private void endVisitType() { fCurrent= (BodyData)fBodies.remove(fBodies.size() - 1); } public boolean visit(FieldDeclaration node) { fBodies.add(fCurrent); fCurrent= new BodyData(node); return true; } public void endVisit(FieldDeclaration node) { if (fCurrent.hasInvocations()) { result.put(node, fCurrent); } endVisitType(); } public boolean visit(FunctionDeclaration node) { fBodies.add(fCurrent); fCurrent= new BodyData(node); return true; } public void endVisit(FunctionDeclaration node) { if (fCurrent.hasInvocations()) { result.put(node, fCurrent); } endVisitType(); } public boolean visit(Initializer node) { fBodies.add(fCurrent); fCurrent= new BodyData(node); return true; } public void endVisit(Initializer node) { if (fCurrent.hasInvocations()) { result.put(node, fCurrent); } endVisitType(); } private boolean matches(IBinding binding) { if (!(binding instanceof IFunctionBinding)) return false; if (BUG_CORE_130317) return fBinding.getKey().equals(((IFunctionBinding)binding).getMethodDeclaration().getKey()); else return fBinding.isEqualTo(((IFunctionBinding)binding).getMethodDeclaration()); } } private static class LocalTypeTargetProvider extends TargetProvider { private IJavaScriptUnit fCUnit; private FunctionDeclaration fDeclaration; private Map fBodies; public LocalTypeTargetProvider(IJavaScriptUnit unit, FunctionDeclaration declaration) { Assert.isNotNull(unit); Assert.isNotNull(declaration); fCUnit= unit; fDeclaration= declaration; } public void initialize() { InvocationFinder finder= new InvocationFinder(fDeclaration.resolveBinding()); ASTNode type= ASTNodes.getParent(fDeclaration, AbstractTypeDeclaration.class); type.accept(finder); fBodies= finder.result; } public IJavaScriptUnit[] getAffectedCompilationUnits(RefactoringStatus status, IProgressMonitor pm) { fastDone(pm); return new IJavaScriptUnit[] { fCUnit }; } public BodyDeclaration[] getAffectedBodyDeclarations(IJavaScriptUnit 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 JavaScriptModelException { return new RefactoringStatus(); } public int getStatusSeverity() { return RefactoringStatus.ERROR; } } private static class MemberTypeTargetProvider extends TargetProvider { private final IFunctionBinding fMethodBinding; private Map fCurrentBodies; public MemberTypeTargetProvider(IFunctionBinding methodBinding) { Assert.isNotNull(methodBinding); fMethodBinding= methodBinding; } public void initialize() { // do nothing. } public IJavaScriptUnit[] getAffectedCompilationUnits(final RefactoringStatus status, IProgressMonitor pm) throws JavaScriptModelException { IFunction method= (IFunction)fMethodBinding.getJavaElement(); Assert.isTrue(method != null); final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(SearchPattern.createPattern(method, IJavaScriptSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE)); engine.setGranularity(RefactoringSearchEngine2.GRANULARITY_COMPILATION_UNIT); engine.setFiltering(true, true); engine.setScope(RefactoringScopeFactory.create(method)); engine.setRequestor(new IRefactoringSearchRequestor() { public SearchMatch acceptSearchMatch(SearchMatch match) { if (match.isInsideDocComment()) return null; if (match.getAccuracy() == SearchMatch.A_INACCURATE) { Object element= match.getElement(); if (element instanceof IJavaScriptElement) { IJavaScriptElement jElement= (IJavaScriptElement)element; IJavaScriptUnit unit= (IJavaScriptUnit)jElement.getAncestor(IJavaScriptElement.JAVASCRIPT_UNIT); if (unit != null) { status.addError(RefactoringCoreMessages.TargetProvider_inaccurate_match, JavaStatusContext.create(unit, new SourceRange(match.getOffset(), match.getLength()))); return null; } } status.addError(RefactoringCoreMessages.TargetProvider_inaccurate_match); return null; } else { return match; } } }); engine.searchPattern(new SubProgressMonitor(pm, 1)); return engine.getAffectedCompilationUnits(); } public BodyDeclaration[] getAffectedBodyDeclarations(IJavaScriptUnit unit, IProgressMonitor pm) { ASTNode root= new RefactoringASTParser(AST.JLS3).parse(unit, true); InvocationFinder finder= new InvocationFinder(fMethodBinding); root.accept(finder); fCurrentBodies= finder.result; Set result= fCurrentBodies.keySet(); fastDone(pm); return (BodyDeclaration[])result.toArray(new BodyDeclaration[result.size()]); } public ASTNode[] getInvocations(BodyDeclaration declaration, IProgressMonitor pm) { BodyData data= (BodyData)fCurrentBodies.get(declaration); Assert.isNotNull(data); fastDone(pm); return data.getInvocations(); } public RefactoringStatus checkActivation() throws JavaScriptModelException { return new RefactoringStatus(); } public int getStatusSeverity() { return RefactoringStatus.ERROR; } } }