blob: a014d07930365b967fa460323bab1282bfcd0d5e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 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.corext.refactoring.code;
import java.util.ArrayList;
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.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEditGroup;
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.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
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.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
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.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParameterizedType;
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.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.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.corext.codemanipulation.StubUtility;
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.ModifierRewrite;
import org.eclipse.jdt.internal.corext.dom.NodeFinder;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptor;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorComment;
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.CompilationUnitChange;
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.SearchUtils;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.internal.ui.JavaUIStatus;
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 ScriptableRefactoring {
private static final String ID_INTRODUCE_FACTORY= "org.eclipse.jdt.ui.introduce.factory"; //$NON-NLS-1$
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();
}
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) {
return parent;
} else if (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();
if (fNewMethodName == null)
fNewMethodName= "create" + fCtorBinding.getName();//$NON-NLS-1$
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= (AbstractTypeDeclaration) ASTNodes.getParent(ctorOwnerName, AbstractTypeDeclaration.class);
fFactoryOwningClass= fCtorOwningClass;
pm.worked(1);
return new RefactoringStatus();
} finally {
pm.done();
}
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkActivation(org.eclipse.core.runtime.IProgressMonitor)
*/
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();
}
}
/**
* Returns 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.
* @return ICompilationUnit[]
*/
private ICompilationUnit[] collectAffectedUnits(SearchResultGroup[] searchHits) {
Collection result= new ArrayList();
boolean hitInFactoryClass= false;
for(int i=0; i < searchHits.length; i++) {
SearchResultGroup rg= searchHits[i];
ICompilationUnit icu= rg.getCompilationUnit();
result.add(icu);
if (icu.equals(fFactoryUnitHandle))
hitInFactoryClass= true;
}
if (!hitInFactoryClass)
result.add(fFactoryUnitHandle);
return (ICompilationUnit[]) result.toArray(new ICompilationUnit[result.size()]);
}
/**
* Returns 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
StringBuffer buf= new StringBuffer();
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());
}
}
/**
* Returns 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 (int i = 0; i < groups.length; i++) {
SearchResultGroup rg= groups[i];
ICompilationUnit unit= rg.getCompilationUnit();
if (unit != null) // Ignore hits within a binary unit
result.add(rg);
else
fCallSitesInBinaryUnits= true;
}
return (SearchResultGroup[]) 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
*/
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.getJavaElement().getJavaProject()));
engine.setStatus(status);
engine.searchPattern(new SubProgressMonitor(pm, 1));
SearchResultGroup[] groups= (SearchResultGroup[]) engine.getResults();
if (groups.length != 0) {
for(int i= 0; i < groups.length; i++) {
SearchMatch[] matches= groups[i].getSearchResults();
for(int j= 0; j < matches.length; j++) {
if (matches[j].getAccuracy() == SearchMatch.A_ACCURATE)
return (IType) matches[j].getElement();
}
}
}
return null;
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkInput(org.eclipse.core.runtime.IProgressMonitor)
*/
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()));
if (fCallSitesInBinaryUnits)
result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.IntroduceFactory_callSitesInBinaryClass));
return result;
} finally {
pm.done();
}
}
/**
* Returns 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= (CompilationUnit) ASTNodes.getParent(fCtorOwningClass, CompilationUnit.class);
MethodDeclaration ctorDecl= (MethodDeclaration) ctorUnit.findDeclaringNode(fCtorBinding.getKey());
if (ctorDecl != null) {
List formalArgs= ctorDecl.parameters();
int i= 0;
for(Iterator iter= formalArgs.iterator(); iter.hasNext(); i++) {
SingleVariableDeclaration svd= (SingleVariableDeclaration) 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
*/
private MethodDeclaration createFactoryMethod(AST ast, IMethodBinding ctorBinding, ASTRewrite unitRewriter) {
MethodDeclaration newMethod= ast.newMethodDeclaration();
SimpleName newMethodName= ast.newSimpleName(fNewMethodName);
ClassInstanceCreation newCtorCall= ast.newClassInstanceCreation();
ReturnStatement ret= ast.newReturnStatement();
Block body= ast.newBlock();
List stmts= body.statements();
String retTypeName= ctorBinding.getName();
createFactoryMethodSignature(ast, newMethod);
newMethod.setName(newMethodName);
newMethod.setBody(body);
ITypeBinding[] ctorOwnerTypeParameters= fCtorBinding.getDeclaringClass().getTypeParameters();
setMethodReturnType(newMethod, retTypeName, ctorOwnerTypeParameters, ast);
newMethod.modifiers().addAll(ASTNodeFactory.newModifiers(ast, Modifier.STATIC | Modifier.PUBLIC));
setCtorTypeArguments(newCtorCall, retTypeName, ctorOwnerTypeParameters, ast);
createFactoryMethodConstructorArgs(ast, newCtorCall);
ret.setExpression(newCtorCall);
stmts.add(ret);
return newMethod;
}
/**
* 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(int i= 0; i < ctorOwnerTypeParameters.length; i++) {
Type typeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameters[i].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(int i= 0; i < ctorOwnerTypeParameters.length; i++) {
Type retTypeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameters[i].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 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);
}
ITypeBinding[] ctorExcepts= fCtorBinding.getExceptionTypes();
List exceptions= newMethod.thrownExceptions();
for(int i=0; i < ctorExcepts.length; i++) {
String excName= fImportRewriter.addImport(ctorExcepts[i]);
exceptions.add(ASTNodeFactory.newName(ast, excName));
}
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) {
ITypeBinding[] ctorOwnerTypeParms= fCtorBinding.getDeclaringClass().getTypeParameters();
List/*<TypeParameter>*/ factoryMethodTypeParms= newMethod.typeParameters();
for(int i= 0; i < ctorOwnerTypeParms.length; i++) {
TypeParameter newParm= ast.newTypeParameter();
ITypeBinding[] parmTypeBounds= ctorOwnerTypeParms[i].getTypeBounds();
List/*<Type>*/ newParmBounds= newParm.typeBounds();
newParm.setName(ast.newSimpleName(ctorOwnerTypeParms[i].getName()));
for(int b=0; b < parmTypeBounds.length; b++) {
if (parmTypeBounds[b].isClass() && parmTypeBounds[b].getSuperclass() == null)
continue;
Type newBound= fImportRewriter.addImport(parmTypeBounds[b], ast);
newParmBounds.add(newBound);
}
factoryMethodTypeParms.add(newParm);
}
}
/**
* Returns 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.
* @param extraDims number of extra array dimensions to add to the resulting type
*/
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 argList= newCtorCall.arguments();
for(int i=0; i < fArgTypes.length; i++) {
ASTNode ctorArg= ast.newSimpleName(fFormalArgNames[i]);
argList.add(ctorArg);
}
}
/**
* Creates and returns a new MethodInvocation node to represent a call to
* the factory method that replaces a direct constructor call.<br>
* The original constructor call is marked as replaced by the new method
* call with the ASTRewrite instance fCtorCallRewriter.
* @param ast utility object used to create AST nodes
* @param ctorCall the ClassInstanceCreation to be marked as replaced
*/
private MethodInvocation createFactoryMethodCall(AST ast, ClassInstanceCreation ctorCall,
ASTRewrite unitRewriter, TextEditGroup gd) {
MethodInvocation factoryMethodCall= ast.newMethodInvocation();
List actualFactoryArgs= factoryMethodCall.arguments();
List actualCtorArgs= ctorCall.arguments();
// 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= (AbstractTypeDeclaration) 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));
for(int i=0; i < actualCtorArgs.size(); i++) {
Expression actualCtorArg= (Expression) actualCtorArgs.get(i);
ASTNode movedArg= unitRewriter.createMoveTarget(actualCtorArg);
actualFactoryArgs.add(movedArg);
// unitRewriter.createMove(actualCtorArg);
// ASTNode rewrittenArg= rewriteArgument(actualCtorArg);
// actualFactoryArgs.add(rewrittenArg);
}
unitRewriter.replace(ctorCall, factoryMethodCall, gd);
return factoryMethodCall;
}
/**
* Returns true iff the given <code>ICompilationUnit</code> is the unit
* containing the original constructor.
* @param unit
*/
private boolean isConstructorUnit(ICompilationUnit unit) {
return unit.equals(ASTCreator.getCu(fCtorOwningClass));
}
/**
* Returns 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.
* Returns 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 unitChange the CompilationUnitChange object for the compilation unit in question
* @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;
}
/**
* Returns 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 (fCU.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();
AST ctorCallAST= unit.getAST();
boolean someCallPatched= false;
for(int i=0; i < hits.length; i++) {
ClassInstanceCreation creation= getCtorCallAt(hits[i].getOffset(), hits[i].getLength(), unit);
if (creation != null) {
TextEditGroup gd= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_replaceCalls);
createFactoryMethodCall(ctorCallAST, creation, unitRewriter, gd);
unitChange.addTextEditGroup(gd);
someCallPatched= true;
}
}
return someCallPatched;
}
/**
* 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 may return null if this is really a constructor->constructor call (e.g. "this(...)")
*/
private ClassInstanceCreation 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),
unitHandle.getSource().substring(start, start + length),
unitHandle.getElementName() }),
null));
if (node instanceof ClassInstanceCreation) {
return (ClassInstanceCreation) node;
} else if (node instanceof VariableDeclaration) {
Expression init= ((VariableDeclaration) node).getInitializer();
if (init instanceof ClassInstanceCreation) {
return (ClassInstanceCreation) init;
} else if (init != null)
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedInitializerNodeType,
new Object[] { init.toString(), unitHandle.getElementName() }),
null));
else
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_noConstructorCallNodeInsideFoundVarbleDecl,
new Object[] { 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 (ClassInstanceCreation) expr;
else
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit,
new Object[] { expr.toString(), unitHandle.getElementName() }),
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
throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit,
new Object[] { node.getClass().getName() + "('" + node.toString() + "')", unitHandle.getElementName() }), //$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
* @param unit
* @param gd the <code>GroupDescription</code> to associate with the changes made
*/
private void createFactoryChange(ASTRewrite unitRewriter, CompilationUnit unit, TextEditGroup gd) {
// ================================================================================
// 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= ASTNodes.getInsertionIndex(fFactoryMethod, factoryOwner.bodyDeclarations());
if (idx < 0) idx= 0; // Guard against bug in getInsertionIndex()
unitRewriter.getListRewrite(factoryOwner, factoryOwner.getBodyDeclarationsProperty()).insertAt(fFactoryMethod, idx, gd);
}
public Change createChange(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceFactory_createChanges, fAllCallsTo.length);
final ITypeBinding binding= fFactoryOwningClass.resolveBinding();
final Map arguments= new HashMap();
String project= null;
IJavaProject javaProject= fCUHandle.getJavaProject();
if (javaProject != null)
project= javaProject.getElementName();
int flags= JavaRefactoringDescriptor.JAR_IMPORTABLE | JavaRefactoringDescriptor.JAR_REFACTORABLE | 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, fCtorOwningClass.getName());
final String header= Messages.format(RefactoringCoreMessages.IntroduceFactory_descriptor_description, new String[] { fNewMethodName, BindingLabelProvider.getBindingLabel(binding, JavaElementLabels.ALL_FULLY_QUALIFIED), BindingLabelProvider.getBindingLabel(fCtorBinding, JavaElementLabels.ALL_FULLY_QUALIFIED)});
final JavaRefactoringDescriptorComment comment= new JavaRefactoringDescriptorComment(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, 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 JavaRefactoringDescriptor descriptor= new JavaRefactoringDescriptor(ID_INTRODUCE_FACTORY, project, description, comment.asString(), arguments, flags);
arguments.put(JavaRefactoringDescriptor.ATTRIBUTE_INPUT, descriptor.elementToHandle(fCUHandle));
arguments.put(JavaRefactoringDescriptor.ATTRIBUTE_NAME, fNewMethodName);
arguments.put(JavaRefactoringDescriptor.ATTRIBUTE_ELEMENT + 1, descriptor.elementToHandle(binding.getJavaElement()));
arguments.put(JavaRefactoringDescriptor.ATTRIBUTE_SELECTION, new Integer(fSelectionStart).toString() + " " + new Integer(fSelectionLength).toString()); //$NON-NLS-1$
arguments.put(ATTRIBUTE_PROTECT, Boolean.valueOf(fProtectConstructor).toString());
final DynamicValidationStateChange result= new DynamicValidationRefactoringChange(descriptor, RefactoringCoreMessages.IntroduceFactory_name);
boolean hitInFactoryClass= false;
boolean hitInCtorClass= false;
for (int i= 0; i < fAllCallsTo.length; i++) {
SearchResultGroup rg= fAllCallsTo[i];
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();
}
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.IRefactoring#getName()
*/
public String getName() {
return RefactoringCoreMessages.IntroduceFactory_name;
}
/**
* Returns the name to be used for the generated factory method.
*/
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
*/
public RefactoringStatus setNewMethodName(String newMethodName) {
Assert.isNotNull(newMethodName);
fNewMethodName = newMethodName;
RefactoringStatus stat= Checks.checkMethodName(newMethodName);
stat.merge(isUniqueMethodName(newMethodName));
return stat;
}
/**
* Returns 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).
* @param methodName
*/
private RefactoringStatus isUniqueMethodName(String methodName) {
boolean conflict= hasMethod(fFactoryOwningClass, methodName);
return conflict ? RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_duplicateMethodName + methodName) : new RefactoringStatus();
}
/**
* Returns true iff the given <code>AbstractTypeDeclaration</code> has a method with
* the given name.
* @param type
* @param name
*/
private boolean hasMethod(AbstractTypeDeclaration type, String name) {
List decls= type.bodyDeclarations();
for (Iterator iter = decls.iterator(); iter.hasNext();) {
BodyDeclaration decl = (BodyDeclaration) iter.next();
if (decl instanceof MethodDeclaration) {
if (((MethodDeclaration) decl).getName().getIdentifier().equals(name))
return true;
}
}
return false;
}
/**
* Returns true iff the selected constructor can be protected.
*/
public boolean canProtectConstructor() {
return !fCtorBinding.isSynthetic() && fFactoryCU.findDeclaringNode(fCtorBinding.getKey()) != null;
}
/**
* 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.
*/
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
*/
public RefactoringStatus setFactoryClass(String fullyQualifiedTypeName) {
IType factoryType;
try {
factoryType= findFactoryClass(fullyQualifiedTypeName);
if (factoryType == null)
return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.IntroduceFactory_noSuchClass, 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= (AbstractTypeDeclaration) 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.
*/
public String getFactoryClassName() {
return fFactoryOwningClass.resolveBinding().getQualifiedName();
}
public RefactoringStatus initialize(final RefactoringArguments arguments) {
if (arguments instanceof JavaRefactoringArguments) {
final JavaRefactoringArguments extended= (JavaRefactoringArguments) arguments;
final String selection= extended.getAttribute(JavaRefactoringDescriptor.ATTRIBUTE_SELECTION);
if (selection != null) {
int offset= -1;
int length= -1;
final StringTokenizer tokenizer= new StringTokenizer(selection);
if (tokenizer.hasMoreTokens())
offset= Integer.valueOf(tokenizer.nextToken()).intValue();
if (tokenizer.hasMoreTokens())
length= Integer.valueOf(tokenizer.nextToken()).intValue();
if (offset >= 0 && length >= 0) {
fSelectionStart= offset;
fSelectionLength= length;
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JavaRefactoringDescriptor.ATTRIBUTE_SELECTION}));
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptor.ATTRIBUTE_SELECTION));
String handle= extended.getAttribute(JavaRefactoringDescriptor.ATTRIBUTE_INPUT);
if (handle != null) {
final IJavaElement element= JavaRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
if (element == null || !element.exists() || element.getElementType() != IJavaElement.COMPILATION_UNIT)
return createInputFatalStatus(element, ID_INTRODUCE_FACTORY);
else {
fCUHandle= (ICompilationUnit) element;
initialize();
}
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptor.ATTRIBUTE_INPUT));
handle= extended.getAttribute(JavaRefactoringDescriptor.ATTRIBUTE_ELEMENT + 1);
if (handle != null) {
final IJavaElement element= JavaRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
if (element == null || !element.exists() || element.getElementType() != IJavaElement.TYPE)
return createInputFatalStatus(element, ID_INTRODUCE_FACTORY);
else {
final IType type= (IType) element;
fFactoryClassName= type.getFullyQualifiedName();
}
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptor.ATTRIBUTE_INPUT));
final String name= extended.getAttribute(JavaRefactoringDescriptor.ATTRIBUTE_NAME);
if (name != null && !"".equals(name)) //$NON-NLS-1$
fNewMethodName= name;
else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptor.ATTRIBUTE_NAME));
final String protect= extended.getAttribute(ATTRIBUTE_PROTECT);
if (protect != null) {
fProtectConstructor= Boolean.valueOf(protect).booleanValue();
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_PROTECT));
} else
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
return new RefactoringStatus();
}
}