blob: 97c35751580126a4bf794d4ea29dbf5994a2cc96 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Samrat Dhillon <samrat.dhillon@gmail.com> - [introduce factory] Introduce Factory on an abstract class adds a statement to create an instance of that class - https://bugs.eclipse.org/bugs/show_bug.cgi?id=395016
* Stephan Herrmann - Contribution for Bug 463360 - [override method][null] generating method override should not create redundant null annotations
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.code;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.MethodRef;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
import org.eclipse.jdt.core.refactoring.descriptors.IntroduceFactoryDescriptor;
import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory;
import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2Core;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.BodyDeclarationRewrite;
import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine2;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationStateChange;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.ASTCreator;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.MethodsSourcePositionComparator;
import org.eclipse.jdt.internal.corext.util.SearchUtils;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.internal.ui.JavaUIStatus;
import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings;
import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider;
/**
* Refactoring class that permits the substitution of a factory method
* for direct calls to a given constructor.
* @author rfuhrer
*/
public class IntroduceFactoryRefactoring extends Refactoring {
private static final String ATTRIBUTE_PROTECT= "protect"; //$NON-NLS-1$
/**
* The handle for the compilation unit holding the selection that was
* passed into this refactoring.
*/
private ICompilationUnit fCUHandle;
/**
* The AST for the compilation unit holding the selection that was
* passed into this refactoring.
*/
private CompilationUnit fCU;
/**
* Handle for compilation unit in which the factory method/class/interface will be
* generated.
*/
private ICompilationUnit fFactoryUnitHandle;
/**
* The start of the original textual selection in effect when this refactoring
* was initiated. If the refactoring was initiated from a structured selection
* (e.g. from the outline view), then this refers to the textual selection that
* corresponds to the structured selection item.
*/
private int fSelectionStart;
/**
* The length of the original textual selection in effect when this refactoring
* was initiated. If the refactoring was initiated from a structured selection
* (e.g. from the outline view), then this refers to the textual selection that
* corresponds to the structured selection item.
*/
private int fSelectionLength;
/**
* The AST node corresponding to the user's textual selection.
*/
private ASTNode fSelectedNode;
/**
* The method binding for the selected constructor.
*/
private IMethodBinding fCtorBinding;
/**
* <code>TypeDeclaration</code> for class containing the constructor to be
* encapsulated.
*/
private AbstractTypeDeclaration fCtorOwningClass;
/**
* The name to be given to the generated factory method.
*/
private String fNewMethodName= null;
/**
* An array of <code>SearchResultGroup</code>'s of all call sites
* that refer to the constructor signature in question.
*/
private SearchResultGroup[] fAllCallsTo;
/**
* The class that will own the factory method/class/interface.
*/
private AbstractTypeDeclaration fFactoryOwningClass;
/**
* The newly-generated factory method.
*/
private MethodDeclaration fFactoryMethod= null;
/**
* An array containing the names of the constructor's formal arguments,
* if available, otherwise "arg1" ... "argN".
*/
private String[] fFormalArgNames= null;
/**
* An array of <code>ITypeBinding</code>'s that describes the types of
* the constructor arguments, in order.
*/
private ITypeBinding[] fArgTypes;
/**
* True iff the given constructor has a varargs signature.
*/
private boolean fCtorIsVarArgs;
/**
* If true, change the visibility of the constructor to protected to better
* encapsulate it.
*/
private boolean fProtectConstructor= true;
/**
* An <code>ImportRewrite</code> that manages imports needed to satisfy
* newly-introduced type references in the <code>ICompilationUnit</code>
* currently being rewritten during <code>createChange()</code>.
*/
private ImportRewrite fImportRewriter;
/**
* True iff there are call sites for the constructor to be encapsulated
* located in binary classes.
*/
private boolean fCallSitesInBinaryUnits;
/**
* <code>CompilationUnit</code> in which the factory is to be created.
*/
private CompilationUnit fFactoryCU;
/**
* The fully qualified name of the factory class. This is only used
* if invoked from a refactoring script.
*/
private String fFactoryClassName;
private int fConstructorVisibility= Modifier.PRIVATE;
/**
* Creates a new <code>IntroduceFactoryRefactoring</code> with the given selection
* on the given compilation unit.
* @param cu the <code>ICompilationUnit</code> in which the user selection was made, or <code>null</code> if invoked from scripting
* @param selectionStart the start of the textual selection in <code>cu</code>
* @param selectionLength the length of the textual selection in <code>cu</code>
*/
public IntroduceFactoryRefactoring(ICompilationUnit cu, int selectionStart, int selectionLength) {
Assert.isTrue(selectionStart >= 0);
Assert.isTrue(selectionLength >= 0);
fSelectionStart= selectionStart;
fSelectionLength= selectionLength;
fCUHandle= cu;
if (cu != null)
initialize();
}
public IntroduceFactoryRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) {
this(null, 0, 0);
RefactoringStatus initializeStatus= initialize(arguments);
status.merge(initializeStatus);
}
private void initialize() {
fCU= ASTCreator.createAST(fCUHandle, null);
}
/**
* Finds and returns the <code>ASTNode</code> for the given source text
* selection, if it is an entire constructor call or the class name portion
* of a constructor call or constructor declaration, or null otherwise.
* @param unit The compilation unit in which the selection was made
* @param offset The textual offset of the start of the selection
* @param length The length of the selection in characters
* @return ClassInstanceCreation or MethodDeclaration
*/
private ASTNode getTargetNode(ICompilationUnit unit, int offset, int length) {
ASTNode node= ASTNodes.getNormalizedNode(NodeFinder.perform(fCU, offset, length));
if (node.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION)
return node;
if (node.getNodeType() == ASTNode.METHOD_DECLARATION && ((MethodDeclaration)node).isConstructor())
return node;
// we have some sub node. Make sure its the right child of the parent
StructuralPropertyDescriptor location= node.getLocationInParent();
ASTNode parent= node.getParent();
if (location == ClassInstanceCreation.TYPE_PROPERTY
|| (location == MethodDeclaration.NAME_PROPERTY && ((MethodDeclaration)parent).isConstructor())) {
return parent;
}
return null;
}
/**
* Determines what kind of AST node was selected, and returns an error status
* if the kind of node is inappropriate for this refactoring.
* @param pm
* @return a RefactoringStatus indicating whether the selection is valid
* @throws JavaModelException
*/
private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaModelException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceFactory_examiningSelection, 2);
fSelectedNode= getTargetNode(fCUHandle, fSelectionStart, fSelectionLength);
if (fSelectedNode == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_notAConstructorInvocation);
// getTargetNode() must return either a ClassInstanceCreation or a
// constructor MethodDeclaration; nothing else.
if (fSelectedNode instanceof ClassInstanceCreation) {
ClassInstanceCreation classInstanceCreation= (ClassInstanceCreation)fSelectedNode;
fCtorBinding= classInstanceCreation.resolveConstructorBinding();
} else if (fSelectedNode instanceof MethodDeclaration) {
MethodDeclaration methodDeclaration= (MethodDeclaration)fSelectedNode;
fCtorBinding= methodDeclaration.resolveBinding();
}
if (fCtorBinding == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unableToResolveConstructorBinding);
// If this constructor is of a generic type, get the generic version,
// not some instantiation thereof.
fCtorBinding= fCtorBinding.getMethodDeclaration();
pm.worked(1);
// We don't handle constructors of nested types at the moment
if (fCtorBinding.getDeclaringClass().isNested())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unsupportedNestedTypes);
ITypeBinding ctorType= fCtorBinding.getDeclaringClass();
IType ctorOwningType= (IType) ctorType.getJavaElement();
if (ctorOwningType.isBinary())
// Can't modify binary CU; don't know what CU to put factory method
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInBinaryClass);
if (ctorOwningType.isEnum())
// Doesn't make sense to encapsulate enum constructors
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInEnum);
// Put the generated factory method inside the type that owns the constructor
fFactoryUnitHandle= ctorOwningType.getCompilationUnit();
fFactoryCU= getASTFor(fFactoryUnitHandle);
Name ctorOwnerName= (Name) NodeFinder.perform(fFactoryCU, ctorOwningType.getNameRange());
fCtorOwningClass= ASTNodes.getParent(ctorOwnerName, AbstractTypeDeclaration.class);
fFactoryOwningClass= fCtorOwningClass;
pm.worked(1);
if (fNewMethodName == null)
return setNewMethodName("create" + fCtorBinding.getName());//$NON-NLS-1$
else
return new RefactoringStatus();
} finally {
pm.done();
}
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkActivation(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceFactory_checkingActivation, 1);
if (!fCUHandle.isStructureKnown())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_syntaxError);
return checkSelection(new SubProgressMonitor(pm, 1));
} finally {
pm.done();
}
}
/**
* @param searchHits
* @return the set of compilation units that will be affected by this
* particular invocation of this refactoring. This in general includes
* the class containing the constructor in question, as well as all
* call sites to the constructor.
*/
private ICompilationUnit[] collectAffectedUnits(SearchResultGroup[] searchHits) {
Collection<ICompilationUnit> result= new ArrayList<>();
boolean hitInFactoryClass= false;
for (SearchResultGroup rg : searchHits) {
ICompilationUnit icu= rg.getCompilationUnit();
result.add(icu);
if (icu.equals(fFactoryUnitHandle))
hitInFactoryClass= true;
}
if (!hitInFactoryClass)
result.add(fFactoryUnitHandle);
return result.toArray(new ICompilationUnit[result.size()]);
}
/**
* @param ctor
* @param methodBinding
* @return a <code>SearchPattern</code> that finds all calls to the constructor
* identified by the argument <code>methodBinding</code>.
*/
private SearchPattern createSearchPattern(IMethod ctor, IMethodBinding methodBinding) {
Assert.isNotNull(methodBinding,
RefactoringCoreMessages.IntroduceFactory_noBindingForSelectedConstructor);
if (ctor != null)
return SearchPattern.createPattern(ctor, IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
else { // perhaps a synthetic method? (but apparently not always... hmmm...)
// Can't find an IMethod for this method, so build a string pattern instead
StringBuilder buf= new StringBuilder();
buf.append(methodBinding.getDeclaringClass().getQualifiedName())
.append("(");//$NON-NLS-1$
for(int i=0; i < fArgTypes.length; i++) {
if (i != 0)
buf.append(","); //$NON-NLS-1$
buf.append(fArgTypes[i].getQualifiedName());
}
buf.append(")"); //$NON-NLS-1$
return SearchPattern.createPattern(buf.toString(), IJavaSearchConstants.CONSTRUCTOR,
IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
}
}
private IJavaSearchScope createSearchScope(IMethod ctor, IMethodBinding binding) throws JavaModelException {
if (ctor != null) {
return RefactoringScopeFactory.create(ctor);
} else {
ITypeBinding type= Bindings.getTopLevelType(binding.getDeclaringClass());
return RefactoringScopeFactory.create(type.getJavaElement());
}
}
/**
* @param groups
* @return an array of <code>SearchResultGroup</code>'s like the argument,
* but omitting those groups that have no corresponding compilation unit
* (i.e. are binary and therefore can't be modified).
*/
private SearchResultGroup[] excludeBinaryUnits(SearchResultGroup[] groups) {
Collection<SearchResultGroup> result= new ArrayList<>();
for (SearchResultGroup rg : groups) {
ICompilationUnit unit= rg.getCompilationUnit();
if (unit != null) // ignore hits within a binary unit
result.add(rg);
else
fCallSitesInBinaryUnits= true;
}
return result.toArray(new SearchResultGroup[result.size()]);
}
/**
* Search for all calls to the given <code>IMethodBinding</code> in the project
* that contains the compilation unit <code>fCUHandle</code>.
* @param methodBinding
* @param pm
* @param status
* @return an array of <code>SearchResultGroup</code>'s that identify the search matches
* @throws JavaModelException
*/
private SearchResultGroup[] searchForCallsTo(IMethodBinding methodBinding, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
IMethod method= (IMethod) methodBinding.getJavaElement();
final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(createSearchPattern(method, methodBinding));
engine.setFiltering(true, true);
engine.setScope(createSearchScope(method, methodBinding));
engine.setStatus(status);
engine.searchPattern(new SubProgressMonitor(pm, 1));
return (SearchResultGroup[]) engine.getResults();
}
/**
* Returns an array of <code>SearchResultGroup</code>'s containing all method
* calls in the Java project that invoke the constructor identified by the given
* <code>IMethodBinding</code>
* @param ctorBinding an <code>IMethodBinding</code> identifying a particular
* constructor signature to search for
* @param pm an <code>IProgressMonitor</code> to use during this potentially
* lengthy operation
* @param status
* @return an array of <code>SearchResultGroup</code>'s identifying all
* calls to the given constructor signature
* @throws JavaModelException
*/
private SearchResultGroup[] findAllCallsTo(IMethodBinding ctorBinding, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
SearchResultGroup[] groups= excludeBinaryUnits(searchForCallsTo(ctorBinding, pm, status));
return groups;
}
private IType findNonPrimaryType(String fullyQualifiedName, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
SearchPattern p= SearchPattern.createPattern(fullyQualifiedName, IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(p);
engine.setFiltering(true, true);
engine.setScope(RefactoringScopeFactory.create(fCtorBinding.getDeclaringClass().getJavaElement().getJavaProject()));
engine.setStatus(status);
engine.searchPattern(new SubProgressMonitor(pm, 1));
SearchResultGroup[] groups= (SearchResultGroup[]) engine.getResults();
for (SearchResultGroup rg : groups) {
for (SearchMatch match : rg.getSearchResults()) {
if (match.getAccuracy() == SearchMatch.A_ACCURATE) {
return (IType) match.getElement();
}
}
}
return null;
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkInput(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceFactory_checking_preconditions, 1);
RefactoringStatus result= new RefactoringStatus();
if (fFactoryClassName != null)
result.merge(setFactoryClass(fFactoryClassName));
if (result.hasFatalError())
return result;
fArgTypes= fCtorBinding.getParameterTypes();
fCtorIsVarArgs= fCtorBinding.isVarargs();
fAllCallsTo= findAllCallsTo(fCtorBinding, pm, result);
fFormalArgNames= findCtorArgNames();
ICompilationUnit[] affectedFiles= collectAffectedUnits(fAllCallsTo);
result.merge(Checks.validateModifiesFiles(ResourceUtil.getFiles(affectedFiles), getValidationContext(), pm));
if (fCallSitesInBinaryUnits)
result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.IntroduceFactory_callSitesInBinaryClass));
if(Modifier.isAbstract(fCtorBinding.getDeclaringClass().getModifiers())){
result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.IntroduceFactory_abstractClass));
}
return result;
} finally {
pm.done();
}
}
/**
* @return an array containing the argument names for the constructor
* identified by <code>fCtorBinding</code>, if available, or default
* names if unavailable (e.g. if the constructor resides in a binary unit).
*/
private String[] findCtorArgNames() {
int numArgs= fCtorBinding.getParameterTypes().length;
String[] names= new String[numArgs];
CompilationUnit ctorUnit= ASTNodes.getParent(fCtorOwningClass, CompilationUnit.class);
MethodDeclaration ctorDecl= (MethodDeclaration) ctorUnit.findDeclaringNode(fCtorBinding.getKey());
if (ctorDecl != null) {
List<SingleVariableDeclaration> formalArgs= ctorDecl.parameters();
int i= 0;
if (formalArgs.size() == 0 && numArgs > 0 && fCtorBinding.isCompactConstructor()) {
ASTNode parent= ctorDecl.getParent();
if (parent instanceof RecordDeclaration) {
RecordDeclaration recDecl= (RecordDeclaration) parent;
formalArgs= recDecl.recordComponents();
}
}
for(Iterator<SingleVariableDeclaration> iter= formalArgs.iterator(); iter.hasNext(); i++) {
SingleVariableDeclaration svd= iter.next();
names[i]= svd.getName().getIdentifier();
}
return names;
}
// Have no way of getting the formal argument names; just fake it.
for(int i=0; i < numArgs; i++)
names[i]= "arg" + (i+1); //$NON-NLS-1$
return names;
}
/**
* Creates and returns a new MethodDeclaration that represents the factory method to be used in
* place of direct calls to the constructor in question.
*
* @param ast An AST used as a factory for various AST nodes
* @param ctorBinding binding for the constructor being wrapped
* @param unitRewriter the ASTRewrite to be used
* @return the new method declaration
* @throws CoreException if an exception occurs while accessing its corresponding resource
*/
private MethodDeclaration createFactoryMethod(AST ast, IMethodBinding ctorBinding, ASTRewrite unitRewriter) throws CoreException{
MethodDeclaration newMethod= ast.newMethodDeclaration();
SimpleName newMethodName= ast.newSimpleName(fNewMethodName);
ClassInstanceCreation newCtorCall= ast.newClassInstanceCreation();
ReturnStatement ret= ast.newReturnStatement();
Block body= ast.newBlock();
List<Statement> stmts= body.statements();
String retTypeName= ctorBinding.getName();
createFactoryMethodSignature(ast, newMethod);
newMethod.setName(newMethodName);
newMethod.setBody(body);
ITypeBinding declaringClass= fCtorBinding.getDeclaringClass();
ITypeBinding[] ctorOwnerTypeParameters= declaringClass.getTypeParameters();
setMethodReturnType(newMethod, retTypeName, ctorOwnerTypeParameters, ast);
newMethod.modifiers().addAll(ASTNodeFactory.newModifiers(ast, Modifier.STATIC | Modifier.PUBLIC));
setCtorTypeArguments(newCtorCall, retTypeName, ctorOwnerTypeParameters, ast);
createFactoryMethodConstructorArgs(ast, newCtorCall);
if (Modifier.isAbstract(declaringClass.getModifiers())) {
AnonymousClassDeclaration decl= ast.newAnonymousClassDeclaration();
CodeGenerationSettings settings= JavaPreferencesSettings.getCodeGenerationSettings(fCUHandle.getJavaProject());
ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fFactoryCU, decl.getStartPosition(), fImportRewriter);
for (IMethodBinding unImplementedMethod : getUnimplementedMethods(declaringClass)) {
MethodDeclaration newMethodDecl= StubUtility2.createImplementationStub(fCUHandle, unitRewriter, fImportRewriter, context,
unImplementedMethod, unImplementedMethod.getDeclaringClass(), settings, false, new NodeFinder(fFactoryCU, decl.getStartPosition(), 0).getCoveringNode());
decl.bodyDeclarations().add(newMethodDecl);
}
newCtorCall.setAnonymousClassDeclaration(decl);
}
ret.setExpression(newCtorCall);
stmts.add(ret);
return newMethod;
}
private IMethodBinding[] getUnimplementedMethods(ITypeBinding binding) {
IMethodBinding[] unimplementedMethods= StubUtility2Core.getUnimplementedMethods(binding, true);
Arrays.sort(unimplementedMethods, new MethodsSourcePositionComparator(binding));
return unimplementedMethods;
}
/**
* Sets the type being instantiated in the given constructor call, including
* specifying any necessary type arguments.
* @param newCtorCall the constructor call to modify
* @param ctorTypeName the simple name of the type being instantiated
* @param ctorOwnerTypeParameters the formal type parameters of the type being
* instantiated
* @param ast utility object used to create AST nodes
*/
private void setCtorTypeArguments(ClassInstanceCreation newCtorCall, String ctorTypeName, ITypeBinding[] ctorOwnerTypeParameters, AST ast) {
if (ctorOwnerTypeParameters.length == 0) // easy, just a simple type
newCtorCall.setType(ASTNodeFactory.newType(ast, ctorTypeName));
else {
Type baseType= ast.newSimpleType(ast.newSimpleName(ctorTypeName));
ParameterizedType newInstantiatedType= ast.newParameterizedType(baseType);
List<Type> newInstTypeArgs= newInstantiatedType.typeArguments();
for (ITypeBinding ctorOwnerTypeParameter : ctorOwnerTypeParameters) {
Type typeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameter.getName());
newInstTypeArgs.add(typeArg);
}
newCtorCall.setType(newInstantiatedType);
}
}
/**
* Sets the return type of the factory method, including any necessary type
* arguments. E.g., for constructor <code>Foo()</code> in <code>Foo&lt;T&gt;</code>,
* the factory method defines a method type parameter <code>&lt;T&gt;</code> and
* returns a <code>Foo&lt;T&gt;</code>.
* @param newMethod the method whose return type is to be set
* @param retTypeName the simple name of the return type (without type parameters)
* @param ctorOwnerTypeParameters the formal type parameters of the type that the
* factory method instantiates (whose constructor is being encapsulated)
* @param ast utility object used to create AST nodes
*/
private void setMethodReturnType(MethodDeclaration newMethod, String retTypeName, ITypeBinding[] ctorOwnerTypeParameters, AST ast) {
if (ctorOwnerTypeParameters.length == 0)
newMethod.setReturnType2(ast.newSimpleType(ast.newSimpleName(retTypeName)));
else {
Type baseType= ast.newSimpleType(ast.newSimpleName(retTypeName));
ParameterizedType newRetType= ast.newParameterizedType(baseType);
List<Type> newRetTypeArgs= newRetType.typeArguments();
for (ITypeBinding ctorOwnerTypeParameter : ctorOwnerTypeParameters) {
Type retTypeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameter.getName());
newRetTypeArgs.add(retTypeArg);
}
newMethod.setReturnType2(newRetType);
}
}
/**
* Creates and adds the necessary argument declarations to the given factory method.<br>
* An argument is needed for each original constructor argument for which the
* evaluation of the actual arguments across all calls was not able to be
* pushed inside the factory method (e.g. arguments with side-effects, references
* to fields if the factory method is to be static or reside in a factory class,
* or arguments that varied across the set of constructor calls).<br>
* <code>fArgTypes</code> identifies such arguments by a <code>null</code> value.
* @param ast utility object used to create AST nodes
* @param newMethod the <code>MethodDeclaration</code> for the factory method
*/
private void createFactoryMethodSignature(AST ast, MethodDeclaration newMethod) {
List<SingleVariableDeclaration> argDecls= newMethod.parameters();
for(int i=0; i < fArgTypes.length; i++) {
SingleVariableDeclaration argDecl= ast.newSingleVariableDeclaration();
Type argType;
if (i == (fArgTypes.length - 1) && fCtorIsVarArgs) {
// The trailing varargs arg has an extra array dimension, compared to
// what we need to pass to setType()...
argType= typeNodeForTypeBinding(fArgTypes[i].getElementType(),
fArgTypes[i].getDimensions()-1, ast);
argDecl.setVarargs(true);
} else
argType= typeNodeForTypeBinding(fArgTypes[i], 0, ast);
argDecl.setName(ast.newSimpleName(fFormalArgNames[i]));
argDecl.setType(argType);
argDecls.add(argDecl);
}
List<Type> exceptions= newMethod.thrownExceptionTypes();
for (ITypeBinding ctorExcept : fCtorBinding.getExceptionTypes()) {
exceptions.add(fImportRewriter.addImport(ctorExcept, ast));
}
copyTypeParameters(ast, newMethod);
}
/**
* Copies the constructor's parent type's type parameters, if any, as
* method type parameters of the new static factory method. (Recall
* that static methods can't refer to type arguments of the enclosing
* class, since they have no instance to serve as a context.)<br>
* Makes sure to copy the bounds from the owning type, to ensure that the
* return type of the factory method satisfies the bounds of the type
* being instantiated.<br>
* E.g., for ctor Foo() in the type Foo<T extends Number>, be sure that
* the factory method is declared as<br>
* <code>static <T extends Number> Foo<T> createFoo()</code><br>
* and not simply<br>
* <code>static <T> Foo<T> createFoo()</code><br>
* or the compiler will bark.
* @param ast utility object needed to create ASTNode's for the new method
* @param newMethod the method onto which to copy the type parameters
*/
private void copyTypeParameters(AST ast, MethodDeclaration newMethod) {
List<TypeParameter> factoryMethodTypeParms= newMethod.typeParameters();
for (ITypeBinding ctorOwnerTypeParm : fCtorBinding.getDeclaringClass().getTypeParameters()) {
TypeParameter newParm= ast.newTypeParameter();
List<Type> newParmBounds= newParm.typeBounds();
newParm.setName(ast.newSimpleName(ctorOwnerTypeParm.getName()));
for (ITypeBinding parmTypeBound : ctorOwnerTypeParm.getTypeBounds()) {
if (parmTypeBound.isClass() && parmTypeBound.getSuperclass() == null) {
continue;
}
Type newBound= fImportRewriter.addImport(parmTypeBound, ast);
newParmBounds.add(newBound);
}
factoryMethodTypeParms.add(newParm);
}
}
/**
* @param argType
* @param extraDims number of extra array dimensions to add to the resulting type
* @param ast
* @return a Type that describes the given ITypeBinding. If the binding
* refers to an object type, use the import rewriter to determine whether
* the reference requires a new import, or instead needs to be qualified.<br>
* Like ASTNodeFactory.newType(), but for the handling of imports.
*/
private Type typeNodeForTypeBinding(ITypeBinding argType, int extraDims, AST ast) {
if (extraDims > 0) {
return ast.newArrayType(typeNodeForTypeBinding(argType, 0, ast), extraDims);
} else if (argType.isArray()) {
Type elementType= typeNodeForTypeBinding(argType.getElementType(), extraDims, ast);
return ast.newArrayType(elementType, argType.getDimensions());
} else {
return fImportRewriter.addImport(argType, ast);
}
}
/**
* Create the list of actual arguments to the constructor call that is
* encapsulated inside the factory method, and associate the arguments
* with the given constructor call object.
* @param ast utility object used to create AST nodes
* @param newCtorCall the newly-generated constructor call to be wrapped inside
* the factory method
*/
private void createFactoryMethodConstructorArgs(AST ast, ClassInstanceCreation newCtorCall) {
List<Expression> argList= newCtorCall.arguments();
for(int i=0; i < fArgTypes.length; i++) {
ASTNode ctorArg= ast.newSimpleName(fFormalArgNames[i]);
argList.add((Expression) ctorArg);
}
}
/**
* Updates the constructor call.
*
* @param ctorCall the ClassInstanceCreation to be marked as replaced
* @param unitRewriter the AST rewriter
* @param gd the edit group to use
*/
private void rewriteFactoryMethodCall(ClassInstanceCreation ctorCall, ASTRewrite unitRewriter, TextEditGroup gd) {
AST ast= unitRewriter.getAST();
MethodInvocation factoryMethodCall= ast.newMethodInvocation();
ASTNode ctorCallParent= ctorCall.getParent();
StructuralPropertyDescriptor ctorCallLocation= ctorCall.getLocationInParent();
if (ctorCallLocation instanceof ChildListPropertyDescriptor) {
ListRewrite ctorCallParentListRewrite= unitRewriter.getListRewrite(ctorCallParent, (ChildListPropertyDescriptor)ctorCallLocation);
int index= ctorCallParentListRewrite.getOriginalList().indexOf(ctorCall);
ctorCall= (ClassInstanceCreation)ctorCallParentListRewrite.getRewrittenList().get(index);
} else {
ctorCall= (ClassInstanceCreation)unitRewriter.get(ctorCallParent, ctorCallLocation);
}
ListRewrite actualFactoryArgs= unitRewriter.getListRewrite(factoryMethodCall, MethodInvocation.ARGUMENTS_PROPERTY);
ListRewrite actualCtorArgs= unitRewriter.getListRewrite(ctorCall, ClassInstanceCreation.ARGUMENTS_PROPERTY);
// Need to use a qualified name for the factory method if we're not
// in the context of the class holding the factory.
AbstractTypeDeclaration callOwner= ASTNodes.getParent(ctorCall, AbstractTypeDeclaration.class);
ITypeBinding callOwnerBinding= callOwner.resolveBinding();
if (callOwnerBinding == null || !Bindings.equals(callOwner.resolveBinding(), fFactoryOwningClass.resolveBinding())) {
String qualifier= fImportRewriter.addImport(fFactoryOwningClass.resolveBinding());
factoryMethodCall.setExpression(ASTNodeFactory.newName(ast, qualifier));
}
factoryMethodCall.setName(ast.newSimpleName(fNewMethodName));
List<Expression> actualCtorArgsList= actualCtorArgs.getRewrittenList();
for (Expression actualCtorArg : actualCtorArgsList) {
ASTNode movedArg;
if (ASTNodes.isExistingNode(actualCtorArg)) {
movedArg= unitRewriter.createMoveTarget(actualCtorArg);
} else {
unitRewriter.remove(actualCtorArg, gd);
movedArg= actualCtorArg;
}
actualFactoryArgs.insertLast(movedArg, gd);
}
unitRewriter.replace(ctorCall, factoryMethodCall, gd);
}
/**
* @param unit
* @return true iff the given <code>ICompilationUnit</code> is the unit
* containing the original constructor
*/
private boolean isConstructorUnit(ICompilationUnit unit) {
return unit.equals(ASTCreator.getCu(fCtorOwningClass));
}
/**
* @return true iff we should actually change the original constructor's
* visibility to <code>protected</code>. This takes into account the user-
* requested mode and whether the constructor's compilation unit is in
* source form.
*/
private boolean shouldProtectConstructor() {
return fProtectConstructor && fCtorOwningClass != null;
}
/**
* Creates and adds the necessary change to make the constructor method protected.
* @param unitAST
* @param unitRewriter
* @param declGD
* @return false iff the constructor didn't exist (i.e. was implicit)
*/
private boolean protectConstructor(CompilationUnit unitAST, ASTRewrite unitRewriter, TextEditGroup declGD) {
MethodDeclaration constructor= (MethodDeclaration) unitAST.findDeclaringNode(fCtorBinding.getKey());
// No need to rewrite the modifiers if the visibility is what we already want it to be.
if (constructor == null || (JdtFlags.getVisibilityCode(constructor)) == fConstructorVisibility)
return false;
ModifierRewrite.create(unitRewriter, constructor).setVisibility(fConstructorVisibility, declGD);
return true;
}
/**
* Add all changes necessary on the <code>ICompilationUnit</code> in the given
* <code>SearchResultGroup</code> to implement the refactoring transformation
* to the given <code>CompilationUnitChange</code>.
* @param rg the <code>SearchResultGroup</code> for which changes should be created
* @param unitHandle
* @param unitChange the CompilationUnitChange object for the compilation unit in question
* @return <code>true</code> iff a change has been added
* @throws CoreException
*/
private boolean addAllChangesFor(SearchResultGroup rg, ICompilationUnit unitHandle, CompilationUnitChange unitChange) throws CoreException {
// ICompilationUnit unitHandle= rg.getCompilationUnit();
Assert.isTrue(rg == null || rg.getCompilationUnit() == unitHandle);
CompilationUnit unit= getASTFor(unitHandle);
ASTRewrite unitRewriter= ASTRewrite.create(unit.getAST());
MultiTextEdit root= new MultiTextEdit();
boolean someChange= false;
unitChange.setEdit(root);
fImportRewriter= StubUtility.createImportRewrite(unit, true);
// First create the factory method
if (unitHandle.equals(fFactoryUnitHandle)) {
TextEditGroup factoryGD= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_addFactoryMethod);
createFactoryChange(unitRewriter, unit, factoryGD);
unitChange.addTextEditGroup(factoryGD);
someChange= true;
}
// Now rewrite all the constructor calls to use the factory method
if (rg != null)
if (replaceConstructorCalls(rg, unit, unitRewriter, unitChange))
someChange= true;
// Finally, make the constructor private, if requested.
if (shouldProtectConstructor() && isConstructorUnit(unitHandle)) {
TextEditGroup declGD= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_protectConstructor);
if (protectConstructor(unit, unitRewriter, declGD)) {
unitChange.addTextEditGroup(declGD);
someChange= true;
}
}
if (someChange) {
root.addChild(unitRewriter.rewriteAST());
root.addChild(fImportRewriter.rewriteImports(null));
}
return someChange;
}
/**
* @param unitHandle
* @return an AST for the given compilation unit handle.<br>
* If this is the unit containing the selection or the unit in which the factory
* is to reside, checks the appropriate field (<code>fCU</code> or <code>fFactoryCU</code>,
* respectively) and initializes the field with a new AST only if not already done.
*/
private CompilationUnit getASTFor(ICompilationUnit unitHandle) {
if (unitHandle.equals(fCUHandle)) { // is this the unit containing the selection?
if (fCU == null) {
fCU= ASTCreator.createAST(unitHandle, null);
if (fCUHandle.equals(fFactoryUnitHandle)) // if selection unit and factory unit are the same...
fFactoryCU= fCU; // ...make sure the factory unit gets initialized
}
return fCU;
} else if (unitHandle.equals(fFactoryUnitHandle)) { // is this the "factory unit"?
if (fFactoryCU == null)
fFactoryCU= ASTCreator.createAST(unitHandle, null);
return fFactoryCU;
} else
return ASTCreator.createAST(unitHandle, null);
}
/**
* Use the given <code>ASTRewrite</code> to replace direct calls to the constructor with calls
* to the newly-created factory method.
*
* @param rg the <code>SearchResultGroup</code> indicating all of the constructor references
* @param unit the <code>CompilationUnit</code> to be rewritten
* @param unitRewriter the rewriter
* @param unitChange the compilation unit change
* @throws CoreException
* @return true iff at least one constructor call site was rewritten.
*/
private boolean replaceConstructorCalls(SearchResultGroup rg, CompilationUnit unit, ASTRewrite unitRewriter, CompilationUnitChange unitChange) throws CoreException {
Assert.isTrue(ASTCreator.getCu(unit).equals(rg.getCompilationUnit()));
SearchMatch[] hits= rg.getSearchResults();
/*
* Sort by descending offset, such that nested constructor calls are processed first. This
* is necessary, since they can only be moved into the factory method invocation after they
* have been rewritten.
*/
Arrays.sort(hits, (m1, m2) -> m2.getOffset() - m1.getOffset());
boolean someCallPatched= false;
for (SearchMatch hit : hits) {
ASTNode ctrCall= getCtorCallAt(hit.getOffset(), hit.getLength(), unit);
if (ctrCall instanceof ClassInstanceCreation) {
TextEditGroup gd= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_replaceCalls);
rewriteFactoryMethodCall((ClassInstanceCreation) ctrCall, unitRewriter, gd);
unitChange.addTextEditGroup(gd);
someCallPatched= true;
} else if (ctrCall instanceof MethodRef) {
TextEditGroup gd= new TextEditGroup(RefactoringCoreMessages.IntroduceFactoryRefactoring_replaceJavadocReference);
rewriteJavadocReference((MethodRef) ctrCall, unitRewriter, gd);
unitChange.addTextEditGroup(gd);
someCallPatched= true;
}
}
return someCallPatched;
}
private void rewriteJavadocReference(MethodRef javadocRef, ASTRewrite unitRewriter, TextEditGroup gd) {
AST ast= unitRewriter.getAST();
unitRewriter.replace(javadocRef.getName(), ast.newSimpleName(fNewMethodName), gd);
}
/**
* Look "in the vicinity" of the given range to find the <code>ClassInstanceCreation</code>
* node that this search hit identified. Necessary because the <code>SearchEngine</code>
* doesn't always cough up text extents that <code>NodeFinder.perform()</code> agrees with.
* @param start
* @param length
* @param unitAST
* @return return a {@link ClassInstanceCreation} or a {@link MethodRef} or <code>null</code> if this is really a constructor->constructor call (e.g. "this(...)")
* @throws CoreException
*/
private ASTNode getCtorCallAt(int start, int length, CompilationUnit unitAST) throws CoreException {
ICompilationUnit unitHandle= ASTCreator.getCu(unitAST);
ASTNode node= NodeFinder.perform(unitAST, start, length);
if (node == null)
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_noASTNodeForConstructorSearchHit,
new Object[] { Integer.toString(start), Integer.toString(start + length),
BasicElementLabels.getJavaCodeString(unitHandle.getSource().substring(start, start + length)),
BasicElementLabels.getFileName(unitHandle) }),
null));
if (node instanceof ClassInstanceCreation) {
if (((ClassInstanceCreation)node).getAnonymousClassDeclaration() != null) {
// Cannot replace anonymous inner class, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=250660
fConstructorVisibility= Modifier.PROTECTED;
return null;
}
return node;
} else if (node instanceof VariableDeclaration) {
Expression init= ((VariableDeclaration) node).getInitializer();
if (init instanceof ClassInstanceCreation) {
return init;
} else if (init != null)
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedInitializerNodeType,
new Object[] { BasicElementLabels.getJavaCodeString(init.toString()), BasicElementLabels.getFileName(unitHandle) }),
null));
else
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_noConstructorCallNodeInsideFoundVarbleDecl,
BasicElementLabels.getJavaCodeString(node.toString())),
null));
} else if (node instanceof ConstructorInvocation) {
// This is a call we can bypass; it's from one constructor flavor
// to another flavor on the same class.
return null;
} else if (node instanceof SuperConstructorInvocation) {
// This is a call we can bypass; it's from one constructor flavor
// to another flavor on the same class.
fConstructorVisibility= Modifier.PROTECTED;
return null;
} else if (node instanceof ExpressionStatement) {
Expression expr= ((ExpressionStatement) node).getExpression();
if (expr instanceof ClassInstanceCreation)
return expr;
else
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit,
new Object[] { BasicElementLabels.getJavaCodeString(expr.toString()), BasicElementLabels.getFileName(unitHandle) }),
null));
} else if (node instanceof SimpleName && (node.getParent() instanceof MethodDeclaration || node.getParent() instanceof AbstractTypeDeclaration)) {
// We seem to have been given a hit for an implicit call to the base-class constructor.
// Do nothing with this (implicit) call, but have to make sure we make the derived class
// doesn't lose access to the base-class constructor (so make it 'protected', not 'private').
fConstructorVisibility= Modifier.PROTECTED;
return null;
} else if (node instanceof MethodRef) {
return node;
} else
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit,
new Object[] { BasicElementLabels.getJavaElementName(node.getClass().getName() + "('" + node.toString() + "')"), BasicElementLabels.getFileName(unitHandle) }), //$NON-NLS-1$ //$NON-NLS-2$
null));
}
/**
* Perform the AST rewriting necessary on the given <code>CompilationUnit</code> to create the
* factory method. The method will reside on the type identified by
* <code>fFactoryOwningClass</code>.
*
* @param unitRewriter the ASTRewrite to be used
* @param unit the <code>CompilationUnit</code> where factory method will be created
* @param gd the <code>GroupDescription</code> to associate with the changes made
* @throws CoreException if an exception occurs while accessing its corresponding resource
*/
private void createFactoryChange(ASTRewrite unitRewriter, CompilationUnit unit, TextEditGroup gd) throws CoreException {
// ================================================================================
// First add the factory itself (method, class, and interface as needed/directed by user)
AST ast= unit.getAST();
fFactoryMethod= createFactoryMethod(ast, fCtorBinding, unitRewriter);
AbstractTypeDeclaration factoryOwner= (AbstractTypeDeclaration) unit.findDeclaringNode(fFactoryOwningClass.resolveBinding().getKey());
fImportRewriter.addImport(fCtorOwningClass.resolveBinding());
int idx= BodyDeclarationRewrite.getInsertionIndex(fFactoryMethod, factoryOwner.bodyDeclarations());
if (idx < 0) idx= 0; // Guard against bug in getInsertionIndex()
unitRewriter.getListRewrite(factoryOwner, factoryOwner.getBodyDeclarationsProperty()).insertAt(fFactoryMethod, idx, gd);
}
@Override
public Change createChange(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceFactory_createChanges, fAllCallsTo.length);
final ITypeBinding binding= fFactoryOwningClass.resolveBinding();
final Map<String, String> arguments= new HashMap<>();
String project= null;
IJavaProject javaProject= fCUHandle.getJavaProject();
if (javaProject != null)
project= javaProject.getElementName();
int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE;
if (binding.isNested() && !binding.isMember())
flags|= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
final String description= Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(fCtorOwningClass.getName().getIdentifier()));
final String header= Messages.format(RefactoringCoreMessages.IntroduceFactory_descriptor_description, new String[] { BasicElementLabels.getJavaElementName(fNewMethodName), BindingLabelProvider.getBindingLabel(binding, JavaElementLabels.ALL_FULLY_QUALIFIED), BindingLabelProvider.getBindingLabel(fCtorBinding, JavaElementLabels.ALL_FULLY_QUALIFIED)});
final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_original_pattern, BindingLabelProvider.getBindingLabel(fCtorBinding, JavaElementLabels.ALL_FULLY_QUALIFIED)));
comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_factory_pattern, BasicElementLabels.getJavaElementName(fNewMethodName)));
comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_owner_pattern, BindingLabelProvider.getBindingLabel(binding, JavaElementLabels.ALL_FULLY_QUALIFIED)));
if (fProtectConstructor)
comment.addSetting(RefactoringCoreMessages.IntroduceFactoryRefactoring_declare_private);
final IntroduceFactoryDescriptor descriptor= RefactoringSignatureDescriptorFactory.createIntroduceFactoryDescriptor(project, description, comment.asString(), arguments, flags);
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project, fCUHandle));
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fNewMethodName);
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1, JavaRefactoringDescriptorUtil.elementToHandle(project, binding.getJavaElement()));
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION, Integer.toString(fSelectionStart) + " " + Integer.toString(fSelectionLength)); //$NON-NLS-1$
arguments.put(ATTRIBUTE_PROTECT, Boolean.toString(fProtectConstructor));
final DynamicValidationStateChange result= new DynamicValidationRefactoringChange(descriptor, RefactoringCoreMessages.IntroduceFactory_name);
boolean hitInFactoryClass= false;
boolean hitInCtorClass= false;
for (SearchResultGroup rg : fAllCallsTo) {
ICompilationUnit unitHandle= rg.getCompilationUnit();
CompilationUnitChange cuChange= new CompilationUnitChange(getName(), unitHandle);
if (addAllChangesFor(rg, unitHandle, cuChange))
result.add(cuChange);
if (unitHandle.equals(fFactoryUnitHandle))
hitInFactoryClass= true;
if (unitHandle.equals(ASTCreator.getCu(fCtorOwningClass)))
hitInCtorClass= true;
pm.worked(1);
if (pm.isCanceled())
throw new OperationCanceledException();
}
if (!hitInFactoryClass) { // Handle factory class if no search hits there
CompilationUnitChange cuChange= new CompilationUnitChange(getName(), fFactoryUnitHandle);
addAllChangesFor(null, fFactoryUnitHandle, cuChange);
result.add(cuChange);
}
if (!hitInCtorClass && !fFactoryUnitHandle.equals(ASTCreator.getCu(fCtorOwningClass))) { // Handle constructor-owning class if no search hits there
CompilationUnitChange cuChange= new CompilationUnitChange(getName(), ASTCreator.getCu(fCtorOwningClass));
addAllChangesFor(null, ASTCreator.getCu(fCtorOwningClass), cuChange);
result.add(cuChange);
}
return result;
} finally {
pm.done();
}
}
@Override
public String getName() {
return RefactoringCoreMessages.IntroduceFactory_name;
}
/**
* Returns the name to be used for the generated factory method.
* @return the new method name
*/
public String getNewMethodName() {
return fNewMethodName;
}
/**
* Sets the name to be used for the generated factory method.<br>
* Returns a <code>RefactoringStatus</code> that indicates whether the
* given name is valid for the new factory method.
* @param newMethodName the name to be used for the generated factory method
* @return the resulting status
*/
public RefactoringStatus setNewMethodName(String newMethodName) {
Assert.isNotNull(newMethodName);
fNewMethodName = newMethodName;
RefactoringStatus stat= Checks.checkMethodName(newMethodName, fCUHandle);
stat.merge(isUniqueMethodName(newMethodName));
return stat;
}
/**
* @param methodName
* @return a <code>RefactoringStatus</code> that identifies whether the
* the name <code>newMethodName</code> is available to use as the name of
* the new factory method within the factory-owner class (either a to-be-
* created factory class or the constructor-owning class, depending on the
* user options).
*/
private RefactoringStatus isUniqueMethodName(String methodName) {
ITypeBinding declaringClass= fCtorBinding.getDeclaringClass();
if (Bindings.findMethodInType(declaringClass, methodName, fCtorBinding.getParameterTypes()) != null) {
String format= Messages.format(RefactoringCoreMessages.IntroduceFactory_duplicateMethodName, BasicElementLabels.getJavaElementName(methodName));
return RefactoringStatus.createErrorStatus(format);
}
return new RefactoringStatus();
}
/**
* Returns true iff the selected constructor can be protected.
* @return return <code>true</code> if the constructor can be made protected
*/
public boolean canProtectConstructor() {
return !fCtorBinding.isSynthetic()
&& fFactoryCU.findDeclaringNode(fCtorBinding.getKey()) != null
&& !fCtorBinding.isCanonicalConstructor();
}
/**
* If the argument is true, change the visibility of the constructor to
* <code>protected</code>, thereby encapsulating it.
* @param protectConstructor
*/
public void setProtectConstructor(boolean protectConstructor) {
fProtectConstructor = protectConstructor;
}
/**
* Returns the project on behalf of which this refactoring was invoked.
* @return returns the Java project
*/
public IJavaProject getProject() {
return fCUHandle.getJavaProject();
}
/**
* Sets the class on which the generated factory method is to be placed.
* @param fullyQualifiedTypeName an <code>IType</code> referring to an existing class
* @return return the resulting status
*/
public RefactoringStatus setFactoryClass(String fullyQualifiedTypeName) {
IType factoryType;
try {
factoryType= findFactoryClass(fullyQualifiedTypeName);
if (factoryType == null)
return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.IntroduceFactory_noSuchClass, BasicElementLabels.getJavaElementName(fullyQualifiedTypeName)));
if (factoryType.isAnnotation())
return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryMethodOnAnnotation);
if (factoryType.isInterface())
return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryMethodOnInterface);
} catch (JavaModelException e) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantCheckForInterface);
}
ICompilationUnit factoryUnitHandle= factoryType.getCompilationUnit();
if (factoryType.isBinary())
return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryInBinaryClass);
else {
try {
if (!fFactoryUnitHandle.equals(factoryUnitHandle)) {
fFactoryCU= getASTFor(factoryUnitHandle);
fFactoryUnitHandle= factoryUnitHandle;
}
fFactoryOwningClass= ASTNodes.getParent(NodeFinder.perform(fFactoryCU, factoryType.getNameRange()), AbstractTypeDeclaration.class);
String factoryPkg= factoryType.getPackageFragment().getElementName();
String ctorPkg= fCtorOwningClass.resolveBinding().getPackage().getName();
if (!factoryPkg.equals(ctorPkg))
fConstructorVisibility= Modifier.PUBLIC;
else if (fFactoryOwningClass != fCtorOwningClass)
fConstructorVisibility= 0; // No such thing as Modifier.PACKAGE...
if (fFactoryOwningClass != fCtorOwningClass)
fConstructorVisibility= 0; // No such thing as Modifier.PACKAGE...
} catch (JavaModelException e) {
return RefactoringStatus.createFatalErrorStatus(e.getMessage());
}
return new RefactoringStatus();
}
}
/**
* Finds the factory class associated with the fully qualified name.
* @param fullyQualifiedTypeName the fully qualified type name
* @return the factory class, or <code>null</code> if not found
* @throws JavaModelException if an error occurs while finding the factory class
*/
private IType findFactoryClass(String fullyQualifiedTypeName) throws JavaModelException {
IType factoryType= getProject().findType(fullyQualifiedTypeName);
if (factoryType == null) // presumably a non-primary type; try the search engine
factoryType= findNonPrimaryType(fullyQualifiedTypeName, new NullProgressMonitor(), new RefactoringStatus());
return factoryType;
}
/**
* Returns the name of the class on which the generated factory method is
* to be placed.
* @return return the factory class name
*/
public String getFactoryClassName() {
return fFactoryOwningClass.resolveBinding().getQualifiedName();
}
private RefactoringStatus initialize(JavaRefactoringArguments arguments) {
final String selection= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION);
if (selection != null) {
int offset= -1;
int length= -1;
final StringTokenizer tokenizer= new StringTokenizer(selection);
if (tokenizer.hasMoreTokens())
offset= Integer.parseInt(tokenizer.nextToken());
if (tokenizer.hasMoreTokens())
length= Integer.parseInt(tokenizer.nextToken());
if (offset >= 0 && length >= 0) {
fSelectionStart= offset;
fSelectionLength= length;
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION}));
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION));
String handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT);
if (handle != null) {
final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false);
if (element == null || !element.exists() || element.getElementType() != IJavaElement.COMPILATION_UNIT)
return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.INTRODUCE_FACTORY);
else {
fCUHandle= (ICompilationUnit) element;
initialize();
}
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1);
if (handle != null) {
final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false);
if (element == null || !element.exists() || element.getElementType() != IJavaElement.TYPE)
return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.INTRODUCE_FACTORY);
else {
final IType type= (IType) element;
fFactoryClassName= type.getFullyQualifiedName();
}
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME);
if (name != null && !"".equals(name)) //$NON-NLS-1$
fNewMethodName= name;
else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME));
final String protect= arguments.getAttribute(ATTRIBUTE_PROTECT);
if (protect != null) {
fProtectConstructor= Boolean.parseBoolean(protect);
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_PROTECT));
return new RefactoringStatus();
}
}