0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.jdt.internal.corext.refactoring.code;
0011:
0012: import java.util.ArrayList;
0013: import java.util.Collection;
0014: import java.util.HashMap;
0015: import java.util.Iterator;
0016: import java.util.List;
0017: import java.util.Map;
0018: import java.util.StringTokenizer;
0019:
0020: import org.eclipse.text.edits.MultiTextEdit;
0021: import org.eclipse.text.edits.TextEditGroup;
0022:
0023: import org.eclipse.core.runtime.Assert;
0024: import org.eclipse.core.runtime.CoreException;
0025: import org.eclipse.core.runtime.IProgressMonitor;
0026: import org.eclipse.core.runtime.IStatus;
0027: import org.eclipse.core.runtime.NullProgressMonitor;
0028: import org.eclipse.core.runtime.OperationCanceledException;
0029: import org.eclipse.core.runtime.SubProgressMonitor;
0030:
0031: import org.eclipse.ltk.core.refactoring.Change;
0032: import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
0033: import org.eclipse.ltk.core.refactoring.RefactoringStatus;
0034: import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
0035:
0036: import org.eclipse.jdt.core.ICompilationUnit;
0037: import org.eclipse.jdt.core.IJavaElement;
0038: import org.eclipse.jdt.core.IJavaProject;
0039: import org.eclipse.jdt.core.IMethod;
0040: import org.eclipse.jdt.core.IType;
0041: import org.eclipse.jdt.core.JavaModelException;
0042: import org.eclipse.jdt.core.dom.AST;
0043: import org.eclipse.jdt.core.dom.ASTNode;
0044: import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
0045: import org.eclipse.jdt.core.dom.Block;
0046: import org.eclipse.jdt.core.dom.ClassInstanceCreation;
0047: import org.eclipse.jdt.core.dom.CompilationUnit;
0048: import org.eclipse.jdt.core.dom.ConstructorInvocation;
0049: import org.eclipse.jdt.core.dom.Expression;
0050: import org.eclipse.jdt.core.dom.ExpressionStatement;
0051: import org.eclipse.jdt.core.dom.IMethodBinding;
0052: import org.eclipse.jdt.core.dom.ITypeBinding;
0053: import org.eclipse.jdt.core.dom.MethodDeclaration;
0054: import org.eclipse.jdt.core.dom.MethodInvocation;
0055: import org.eclipse.jdt.core.dom.MethodRef;
0056: import org.eclipse.jdt.core.dom.Modifier;
0057: import org.eclipse.jdt.core.dom.Name;
0058: import org.eclipse.jdt.core.dom.ParameterizedType;
0059: import org.eclipse.jdt.core.dom.ReturnStatement;
0060: import org.eclipse.jdt.core.dom.SimpleName;
0061: import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
0062: import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
0063: import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
0064: import org.eclipse.jdt.core.dom.Type;
0065: import org.eclipse.jdt.core.dom.TypeParameter;
0066: import org.eclipse.jdt.core.dom.VariableDeclaration;
0067: import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
0068: import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
0069: import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
0070: import org.eclipse.jdt.core.refactoring.descriptors.IntroduceFactoryDescriptor;
0071: import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor;
0072: import org.eclipse.jdt.core.search.IJavaSearchConstants;
0073: import org.eclipse.jdt.core.search.IJavaSearchScope;
0074: import org.eclipse.jdt.core.search.SearchMatch;
0075: import org.eclipse.jdt.core.search.SearchPattern;
0076:
0077: import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
0078: import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
0079: import org.eclipse.jdt.internal.corext.dom.ASTNodes;
0080: import org.eclipse.jdt.internal.corext.dom.Bindings;
0081: import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
0082: import org.eclipse.jdt.internal.corext.dom.NodeFinder;
0083: import org.eclipse.jdt.internal.corext.refactoring.Checks;
0084: import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
0085: import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
0086: import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil;
0087: import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
0088: import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory;
0089: import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine2;
0090: import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
0091: import org.eclipse.jdt.internal.corext.refactoring.changes.CompilationUnitChange;
0092: import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
0093: import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationStateChange;
0094: import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.ASTCreator;
0095: import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
0096: import org.eclipse.jdt.internal.corext.util.JdtFlags;
0097: import org.eclipse.jdt.internal.corext.util.Messages;
0098: import org.eclipse.jdt.internal.corext.util.SearchUtils;
0099:
0100: import org.eclipse.jdt.ui.JavaElementLabels;
0101:
0102: import org.eclipse.jdt.internal.ui.JavaUIStatus;
0103: import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider;
0104:
0105: /**
0106: * Refactoring class that permits the substitution of a factory method
0107: * for direct calls to a given constructor.
0108: * @author rfuhrer
0109: */
0110: public class IntroduceFactoryRefactoring extends ScriptableRefactoring {
0111:
0112: private static final String ATTRIBUTE_PROTECT = "protect"; //$NON-NLS-1$
0113:
0114: /**
0115: * The handle for the compilation unit holding the selection that was
0116: * passed into this refactoring.
0117: */
0118: private ICompilationUnit fCUHandle;
0119:
0120: /**
0121: * The AST for the compilation unit holding the selection that was
0122: * passed into this refactoring.
0123: */
0124: private CompilationUnit fCU;
0125:
0126: /**
0127: * Handle for compilation unit in which the factory method/class/interface will be
0128: * generated.
0129: */
0130: private ICompilationUnit fFactoryUnitHandle;
0131:
0132: /**
0133: * The start of the original textual selection in effect when this refactoring
0134: * was initiated. If the refactoring was initiated from a structured selection
0135: * (e.g. from the outline view), then this refers to the textual selection that
0136: * corresponds to the structured selection item.
0137: */
0138: private int fSelectionStart;
0139:
0140: /**
0141: * The length of the original textual selection in effect when this refactoring
0142: * was initiated. If the refactoring was initiated from a structured selection
0143: * (e.g. from the outline view), then this refers to the textual selection that
0144: * corresponds to the structured selection item.
0145: */
0146: private int fSelectionLength;
0147:
0148: /**
0149: * The AST node corresponding to the user's textual selection.
0150: */
0151: private ASTNode fSelectedNode;
0152:
0153: /**
0154: * The method binding for the selected constructor.
0155: */
0156: private IMethodBinding fCtorBinding;
0157:
0158: /**
0159: * <code>TypeDeclaration</code> for class containing the constructor to be
0160: * encapsulated.
0161: */
0162: private AbstractTypeDeclaration fCtorOwningClass;
0163:
0164: /**
0165: * The name to be given to the generated factory method.
0166: */
0167: private String fNewMethodName = null;
0168:
0169: /**
0170: * An array of <code>SearchResultGroup</code>'s of all call sites
0171: * that refer to the constructor signature in question.
0172: */
0173: private SearchResultGroup[] fAllCallsTo;
0174:
0175: /**
0176: * The class that will own the factory method/class/interface.
0177: */
0178: private AbstractTypeDeclaration fFactoryOwningClass;
0179:
0180: /**
0181: * The newly-generated factory method.
0182: */
0183: private MethodDeclaration fFactoryMethod = null;
0184:
0185: /**
0186: * An array containing the names of the constructor's formal arguments,
0187: * if available, otherwise "arg1" ... "argN".
0188: */
0189: private String[] fFormalArgNames = null;
0190:
0191: /**
0192: * An array of <code>ITypeBinding</code>'s that describes the types of
0193: * the constructor arguments, in order.
0194: */
0195: private ITypeBinding[] fArgTypes;
0196:
0197: /**
0198: * True iff the given constructor has a varargs signature.
0199: */
0200: private boolean fCtorIsVarArgs;
0201:
0202: /**
0203: * If true, change the visibility of the constructor to protected to better
0204: * encapsulate it.
0205: */
0206: private boolean fProtectConstructor = true;
0207:
0208: /**
0209: * An <code>ImportRewrite</code> that manages imports needed to satisfy
0210: * newly-introduced type references in the <code>ICompilationUnit</code>
0211: * currently being rewritten during <code>createChange()</code>.
0212: */
0213: private ImportRewrite fImportRewriter;
0214:
0215: /**
0216: * True iff there are call sites for the constructor to be encapsulated
0217: * located in binary classes.
0218: */
0219: private boolean fCallSitesInBinaryUnits;
0220:
0221: /**
0222: * <code>CompilationUnit</code> in which the factory is to be created.
0223: */
0224: private CompilationUnit fFactoryCU;
0225:
0226: /**
0227: * The fully qualified name of the factory class. This is only used
0228: * if invoked from a refactoring script.
0229: */
0230: private String fFactoryClassName;
0231:
0232: private int fConstructorVisibility = Modifier.PRIVATE;
0233:
0234: /**
0235: * Creates a new <code>IntroduceFactoryRefactoring</code> with the given selection
0236: * on the given compilation unit.
0237: * @param cu the <code>ICompilationUnit</code> in which the user selection was made, or <code>null</code> if invoked from scripting
0238: * @param selectionStart the start of the textual selection in <code>cu</code>
0239: * @param selectionLength the length of the textual selection in <code>cu</code>
0240: */
0241: public IntroduceFactoryRefactoring(ICompilationUnit cu,
0242: int selectionStart, int selectionLength) {
0243: Assert.isTrue(selectionStart >= 0);
0244: Assert.isTrue(selectionLength >= 0);
0245: fSelectionStart = selectionStart;
0246: fSelectionLength = selectionLength;
0247: fCUHandle = cu;
0248: if (cu != null)
0249: initialize();
0250: }
0251:
0252: private void initialize() {
0253: fCU = ASTCreator.createAST(fCUHandle, null);
0254: }
0255:
0256: /**
0257: * Finds and returns the <code>ASTNode</code> for the given source text
0258: * selection, if it is an entire constructor call or the class name portion
0259: * of a constructor call or constructor declaration, or null otherwise.
0260: * @param unit The compilation unit in which the selection was made
0261: * @param offset The textual offset of the start of the selection
0262: * @param length The length of the selection in characters
0263: * @return ClassInstanceCreation or MethodDeclaration
0264: */
0265: private ASTNode getTargetNode(ICompilationUnit unit, int offset,
0266: int length) {
0267: ASTNode node = ASTNodes.getNormalizedNode(NodeFinder.perform(
0268: fCU, offset, length));
0269: if (node.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION)
0270: return node;
0271: if (node.getNodeType() == ASTNode.METHOD_DECLARATION
0272: && ((MethodDeclaration) node).isConstructor())
0273: return node;
0274: // we have some sub node. Make sure its the right child of the parent
0275: StructuralPropertyDescriptor location = node
0276: .getLocationInParent();
0277: ASTNode parent = node.getParent();
0278: if (location == ClassInstanceCreation.TYPE_PROPERTY) {
0279: return parent;
0280: } else if (location == MethodDeclaration.NAME_PROPERTY
0281: && ((MethodDeclaration) parent).isConstructor()) {
0282: return parent;
0283: }
0284: return null;
0285: }
0286:
0287: /**
0288: * Determines what kind of AST node was selected, and returns an error status
0289: * if the kind of node is inappropriate for this refactoring.
0290: * @param pm
0291: * @return a RefactoringStatus indicating whether the selection is valid
0292: * @throws JavaModelException
0293: */
0294: private RefactoringStatus checkSelection(IProgressMonitor pm)
0295: throws JavaModelException {
0296: try {
0297: pm
0298: .beginTask(
0299: RefactoringCoreMessages.IntroduceFactory_examiningSelection,
0300: 2);
0301:
0302: fSelectedNode = getTargetNode(fCUHandle, fSelectionStart,
0303: fSelectionLength);
0304:
0305: if (fSelectedNode == null)
0306: return RefactoringStatus
0307: .createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_notAConstructorInvocation);
0308:
0309: // getTargetNode() must return either a ClassInstanceCreation or a
0310: // constructor MethodDeclaration; nothing else.
0311: if (fSelectedNode instanceof ClassInstanceCreation) {
0312: ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation) fSelectedNode;
0313: fCtorBinding = classInstanceCreation
0314: .resolveConstructorBinding();
0315: } else if (fSelectedNode instanceof MethodDeclaration) {
0316: MethodDeclaration methodDeclaration = (MethodDeclaration) fSelectedNode;
0317: fCtorBinding = methodDeclaration.resolveBinding();
0318: }
0319:
0320: if (fCtorBinding == null)
0321: return RefactoringStatus
0322: .createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unableToResolveConstructorBinding);
0323:
0324: // If this constructor is of a generic type, get the generic version,
0325: // not some instantiation thereof.
0326: fCtorBinding = fCtorBinding.getMethodDeclaration();
0327:
0328: if (fNewMethodName == null)
0329: fNewMethodName = "create" + fCtorBinding.getName();//$NON-NLS-1$
0330:
0331: pm.worked(1);
0332:
0333: // We don't handle constructors of nested types at the moment
0334: if (fCtorBinding.getDeclaringClass().isNested())
0335: return RefactoringStatus
0336: .createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unsupportedNestedTypes);
0337:
0338: ITypeBinding ctorType = fCtorBinding.getDeclaringClass();
0339: IType ctorOwningType = (IType) ctorType.getJavaElement();
0340:
0341: if (ctorOwningType.isBinary())
0342: // Can't modify binary CU; don't know what CU to put factory method
0343: return RefactoringStatus
0344: .createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInBinaryClass);
0345: if (ctorOwningType.isEnum())
0346: // Doesn't make sense to encapsulate enum constructors
0347: return RefactoringStatus
0348: .createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInEnum);
0349:
0350: // Put the generated factory method inside the type that owns the constructor
0351: fFactoryUnitHandle = ctorOwningType.getCompilationUnit();
0352: fFactoryCU = getASTFor(fFactoryUnitHandle);
0353:
0354: Name ctorOwnerName = (Name) NodeFinder.perform(fFactoryCU,
0355: ctorOwningType.getNameRange());
0356:
0357: fCtorOwningClass = (AbstractTypeDeclaration) ASTNodes
0358: .getParent(ctorOwnerName,
0359: AbstractTypeDeclaration.class);
0360: fFactoryOwningClass = fCtorOwningClass;
0361:
0362: pm.worked(1);
0363:
0364: return new RefactoringStatus();
0365: } finally {
0366: pm.done();
0367: }
0368: }
0369:
0370: /*
0371: * @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkActivation(org.eclipse.core.runtime.IProgressMonitor)
0372: */
0373: public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
0374: throws CoreException {
0375: try {
0376: pm
0377: .beginTask(
0378: RefactoringCoreMessages.IntroduceFactory_checkingActivation,
0379: 1);
0380:
0381: if (!fCUHandle.isStructureKnown())
0382: return RefactoringStatus
0383: .createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_syntaxError);
0384:
0385: return checkSelection(new SubProgressMonitor(pm, 1));
0386: } finally {
0387: pm.done();
0388: }
0389: }
0390:
0391: /**
0392: * @param searchHits
0393: * @return the set of compilation units that will be affected by this
0394: * particular invocation of this refactoring. This in general includes
0395: * the class containing the constructor in question, as well as all
0396: * call sites to the constructor.
0397: */
0398: private ICompilationUnit[] collectAffectedUnits(
0399: SearchResultGroup[] searchHits) {
0400: Collection result = new ArrayList();
0401: boolean hitInFactoryClass = false;
0402:
0403: for (int i = 0; i < searchHits.length; i++) {
0404: SearchResultGroup rg = searchHits[i];
0405: ICompilationUnit icu = rg.getCompilationUnit();
0406:
0407: result.add(icu);
0408: if (icu.equals(fFactoryUnitHandle))
0409: hitInFactoryClass = true;
0410: }
0411: if (!hitInFactoryClass)
0412: result.add(fFactoryUnitHandle);
0413: return (ICompilationUnit[]) result
0414: .toArray(new ICompilationUnit[result.size()]);
0415: }
0416:
0417: /**
0418: * @param ctor
0419: * @param methodBinding
0420: * @return a <code>SearchPattern</code> that finds all calls to the constructor
0421: * identified by the argument <code>methodBinding</code>.
0422: */
0423: private SearchPattern createSearchPattern(IMethod ctor,
0424: IMethodBinding methodBinding) {
0425: Assert
0426: .isNotNull(
0427: methodBinding,
0428: RefactoringCoreMessages.IntroduceFactory_noBindingForSelectedConstructor);
0429:
0430: if (ctor != null)
0431: return SearchPattern.createPattern(ctor,
0432: IJavaSearchConstants.REFERENCES,
0433: SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
0434: else { // perhaps a synthetic method? (but apparently not always... hmmm...)
0435: // Can't find an IMethod for this method, so build a string pattern instead
0436: StringBuffer buf = new StringBuffer();
0437:
0438: buf.append(
0439: methodBinding.getDeclaringClass()
0440: .getQualifiedName()).append("(");//$NON-NLS-1$
0441: for (int i = 0; i < fArgTypes.length; i++) {
0442: if (i != 0)
0443: buf.append(","); //$NON-NLS-1$
0444: buf.append(fArgTypes[i].getQualifiedName());
0445: }
0446: buf.append(")"); //$NON-NLS-1$
0447: return SearchPattern.createPattern(buf.toString(),
0448: IJavaSearchConstants.CONSTRUCTOR,
0449: IJavaSearchConstants.REFERENCES,
0450: SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
0451: }
0452: }
0453:
0454: private IJavaSearchScope createSearchScope(IMethod ctor,
0455: IMethodBinding binding) throws JavaModelException {
0456: if (ctor != null) {
0457: return RefactoringScopeFactory.create(ctor);
0458: } else {
0459: ITypeBinding type = Bindings.getTopLevelType(binding
0460: .getDeclaringClass());
0461: return RefactoringScopeFactory
0462: .create(type.getJavaElement());
0463: }
0464: }
0465:
0466: /**
0467: * @param groups
0468: * @return an array of <code>SearchResultGroup</code>'s like the argument,
0469: * but omitting those groups that have no corresponding compilation unit
0470: * (i.e. are binary and therefore can't be modified).
0471: */
0472: private SearchResultGroup[] excludeBinaryUnits(
0473: SearchResultGroup[] groups) {
0474: Collection/*<SearchResultGroup>*/result = new ArrayList();
0475:
0476: for (int i = 0; i < groups.length; i++) {
0477: SearchResultGroup rg = groups[i];
0478: ICompilationUnit unit = rg.getCompilationUnit();
0479:
0480: if (unit != null) // Ignore hits within a binary unit
0481: result.add(rg);
0482: else
0483: fCallSitesInBinaryUnits = true;
0484: }
0485: return (SearchResultGroup[]) result
0486: .toArray(new SearchResultGroup[result.size()]);
0487: }
0488:
0489: /**
0490: * Search for all calls to the given <code>IMethodBinding</code> in the project
0491: * that contains the compilation unit <code>fCUHandle</code>.
0492: * @param methodBinding
0493: * @param pm
0494: * @param status
0495: * @return an array of <code>SearchResultGroup</code>'s that identify the search matches
0496: * @throws JavaModelException
0497: */
0498: private SearchResultGroup[] searchForCallsTo(
0499: IMethodBinding methodBinding, IProgressMonitor pm,
0500: RefactoringStatus status) throws JavaModelException {
0501: IMethod method = (IMethod) methodBinding.getJavaElement();
0502: final RefactoringSearchEngine2 engine = new RefactoringSearchEngine2(
0503: createSearchPattern(method, methodBinding));
0504: engine.setFiltering(true, true);
0505: engine.setScope(createSearchScope(method, methodBinding));
0506: engine.setStatus(status);
0507: engine.searchPattern(new SubProgressMonitor(pm, 1));
0508: return (SearchResultGroup[]) engine.getResults();
0509: }
0510:
0511: /**
0512: * Returns an array of <code>SearchResultGroup</code>'s containing all method
0513: * calls in the Java project that invoke the constructor identified by the given
0514: * <code>IMethodBinding</code>
0515: * @param ctorBinding an <code>IMethodBinding</code> identifying a particular
0516: * constructor signature to search for
0517: * @param pm an <code>IProgressMonitor</code> to use during this potentially
0518: * lengthy operation
0519: * @param status
0520: * @return an array of <code>SearchResultGroup</code>'s identifying all
0521: * calls to the given constructor signature
0522: * @throws JavaModelException
0523: */
0524: private SearchResultGroup[] findAllCallsTo(
0525: IMethodBinding ctorBinding, IProgressMonitor pm,
0526: RefactoringStatus status) throws JavaModelException {
0527: SearchResultGroup[] groups = excludeBinaryUnits(searchForCallsTo(
0528: ctorBinding, pm, status));
0529:
0530: return groups;
0531: }
0532:
0533: private IType findNonPrimaryType(String fullyQualifiedName,
0534: IProgressMonitor pm, RefactoringStatus status)
0535: throws JavaModelException {
0536: SearchPattern p = SearchPattern.createPattern(
0537: fullyQualifiedName, IJavaSearchConstants.TYPE,
0538: IJavaSearchConstants.DECLARATIONS,
0539: SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
0540: final RefactoringSearchEngine2 engine = new RefactoringSearchEngine2(
0541: p);
0542:
0543: engine.setFiltering(true, true);
0544: engine.setScope(RefactoringScopeFactory.create(fCtorBinding
0545: .getJavaElement().getJavaProject()));
0546: engine.setStatus(status);
0547: engine.searchPattern(new SubProgressMonitor(pm, 1));
0548:
0549: SearchResultGroup[] groups = (SearchResultGroup[]) engine
0550: .getResults();
0551:
0552: if (groups.length != 0) {
0553: for (int i = 0; i < groups.length; i++) {
0554: SearchMatch[] matches = groups[i].getSearchResults();
0555: for (int j = 0; j < matches.length; j++) {
0556: if (matches[j].getAccuracy() == SearchMatch.A_ACCURATE)
0557: return (IType) matches[j].getElement();
0558: }
0559: }
0560: }
0561: return null;
0562: }
0563:
0564: /*
0565: * @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkInput(org.eclipse.core.runtime.IProgressMonitor)
0566: */
0567: public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
0568: throws CoreException {
0569: try {
0570: pm
0571: .beginTask(
0572: RefactoringCoreMessages.IntroduceFactory_checking_preconditions,
0573: 1);
0574: RefactoringStatus result = new RefactoringStatus();
0575:
0576: if (fFactoryClassName != null)
0577: result.merge(setFactoryClass(fFactoryClassName));
0578: if (result.hasFatalError())
0579: return result;
0580: fArgTypes = fCtorBinding.getParameterTypes();
0581: fCtorIsVarArgs = fCtorBinding.isVarargs();
0582: fAllCallsTo = findAllCallsTo(fCtorBinding, pm, result);
0583: fFormalArgNames = findCtorArgNames();
0584:
0585: ICompilationUnit[] affectedFiles = collectAffectedUnits(fAllCallsTo);
0586: result.merge(Checks.validateModifiesFiles(ResourceUtil
0587: .getFiles(affectedFiles), getValidationContext()));
0588:
0589: if (fCallSitesInBinaryUnits)
0590: result
0591: .merge(RefactoringStatus
0592: .createWarningStatus(RefactoringCoreMessages.IntroduceFactory_callSitesInBinaryClass));
0593:
0594: return result;
0595: } finally {
0596: pm.done();
0597: }
0598: }
0599:
0600: /**
0601: * @return an array containing the argument names for the constructor
0602: * identified by <code>fCtorBinding</code>, if available, or default
0603: * names if unavailable (e.g. if the constructor resides in a binary unit).
0604: */
0605: private String[] findCtorArgNames() {
0606: int numArgs = fCtorBinding.getParameterTypes().length;
0607: String[] names = new String[numArgs];
0608:
0609: CompilationUnit ctorUnit = (CompilationUnit) ASTNodes
0610: .getParent(fCtorOwningClass, CompilationUnit.class);
0611: MethodDeclaration ctorDecl = (MethodDeclaration) ctorUnit
0612: .findDeclaringNode(fCtorBinding.getKey());
0613:
0614: if (ctorDecl != null) {
0615: List formalArgs = ctorDecl.parameters();
0616: int i = 0;
0617:
0618: for (Iterator iter = formalArgs.iterator(); iter.hasNext(); i++) {
0619: SingleVariableDeclaration svd = (SingleVariableDeclaration) iter
0620: .next();
0621:
0622: names[i] = svd.getName().getIdentifier();
0623: }
0624: return names;
0625: }
0626:
0627: // Have no way of getting the formal argument names; just fake it.
0628: for (int i = 0; i < numArgs; i++)
0629: names[i] = "arg" + (i + 1); //$NON-NLS-1$
0630:
0631: return names;
0632: }
0633:
0634: /**
0635: * Creates and returns a new MethodDeclaration that represents the factory
0636: * method to be used in place of direct calls to the constructor in question.
0637: * @param ast An AST used as a factory for various AST nodes
0638: * @param ctorBinding binding for the constructor being wrapped
0639: * @param unitRewriter the ASTRewrite to be used
0640: * @return the new method declaration
0641: */
0642: private MethodDeclaration createFactoryMethod(AST ast,
0643: IMethodBinding ctorBinding, ASTRewrite unitRewriter) {
0644: MethodDeclaration newMethod = ast.newMethodDeclaration();
0645: SimpleName newMethodName = ast.newSimpleName(fNewMethodName);
0646: ClassInstanceCreation newCtorCall = ast
0647: .newClassInstanceCreation();
0648: ReturnStatement ret = ast.newReturnStatement();
0649: Block body = ast.newBlock();
0650: List stmts = body.statements();
0651: String retTypeName = ctorBinding.getName();
0652:
0653: createFactoryMethodSignature(ast, newMethod);
0654:
0655: newMethod.setName(newMethodName);
0656: newMethod.setBody(body);
0657:
0658: ITypeBinding[] ctorOwnerTypeParameters = fCtorBinding
0659: .getDeclaringClass().getTypeParameters();
0660:
0661: setMethodReturnType(newMethod, retTypeName,
0662: ctorOwnerTypeParameters, ast);
0663:
0664: newMethod.modifiers().addAll(
0665: ASTNodeFactory.newModifiers(ast, Modifier.STATIC
0666: | Modifier.PUBLIC));
0667:
0668: setCtorTypeArguments(newCtorCall, retTypeName,
0669: ctorOwnerTypeParameters, ast);
0670:
0671: createFactoryMethodConstructorArgs(ast, newCtorCall);
0672:
0673: ret.setExpression(newCtorCall);
0674: stmts.add(ret);
0675:
0676: return newMethod;
0677: }
0678:
0679: /**
0680: * Sets the type being instantiated in the given constructor call, including
0681: * specifying any necessary type arguments.
0682: * @param newCtorCall the constructor call to modify
0683: * @param ctorTypeName the simple name of the type being instantiated
0684: * @param ctorOwnerTypeParameters the formal type parameters of the type being
0685: * instantiated
0686: * @param ast utility object used to create AST nodes
0687: */
0688: private void setCtorTypeArguments(
0689: ClassInstanceCreation newCtorCall, String ctorTypeName,
0690: ITypeBinding[] ctorOwnerTypeParameters, AST ast) {
0691: if (ctorOwnerTypeParameters.length == 0) // easy, just a simple type
0692: newCtorCall.setType(ASTNodeFactory.newType(ast,
0693: ctorTypeName));
0694: else {
0695: Type baseType = ast.newSimpleType(ast
0696: .newSimpleName(ctorTypeName));
0697: ParameterizedType newInstantiatedType = ast
0698: .newParameterizedType(baseType);
0699: List/*<Type>*/newInstTypeArgs = newInstantiatedType
0700: .typeArguments();
0701:
0702: for (int i = 0; i < ctorOwnerTypeParameters.length; i++) {
0703: Type typeArg = ASTNodeFactory.newType(ast,
0704: ctorOwnerTypeParameters[i].getName());
0705:
0706: newInstTypeArgs.add(typeArg);
0707: }
0708: newCtorCall.setType(newInstantiatedType);
0709: }
0710: }
0711:
0712: /**
0713: * Sets the return type of the factory method, including any necessary type
0714: * arguments. E.g., for constructor <code>Foo()</code> in <code>Foo<T></code>,
0715: * the factory method defines a method type parameter <code><T></code> and
0716: * returns a <code>Foo<T></code>.
0717: * @param newMethod the method whose return type is to be set
0718: * @param retTypeName the simple name of the return type (without type parameters)
0719: * @param ctorOwnerTypeParameters the formal type parameters of the type that the
0720: * factory method instantiates (whose constructor is being encapsulated)
0721: * @param ast utility object used to create AST nodes
0722: */
0723: private void setMethodReturnType(MethodDeclaration newMethod,
0724: String retTypeName, ITypeBinding[] ctorOwnerTypeParameters,
0725: AST ast) {
0726: if (ctorOwnerTypeParameters.length == 0)
0727: newMethod.setReturnType2(ast.newSimpleType(ast
0728: .newSimpleName(retTypeName)));
0729: else {
0730: Type baseType = ast.newSimpleType(ast
0731: .newSimpleName(retTypeName));
0732: ParameterizedType newRetType = ast
0733: .newParameterizedType(baseType);
0734: List/*<Type>*/newRetTypeArgs = newRetType.typeArguments();
0735:
0736: for (int i = 0; i < ctorOwnerTypeParameters.length; i++) {
0737: Type retTypeArg = ASTNodeFactory.newType(ast,
0738: ctorOwnerTypeParameters[i].getName());
0739:
0740: newRetTypeArgs.add(retTypeArg);
0741: }
0742: newMethod.setReturnType2(newRetType);
0743: }
0744: }
0745:
0746: /**
0747: * Creates and adds the necessary argument declarations to the given factory method.<br>
0748: * An argument is needed for each original constructor argument for which the
0749: * evaluation of the actual arguments across all calls was not able to be
0750: * pushed inside the factory method (e.g. arguments with side-effects, references
0751: * to fields if the factory method is to be static or reside in a factory class,
0752: * or arguments that varied across the set of constructor calls).<br>
0753: * <code>fArgTypes</code> identifies such arguments by a <code>null</code> value.
0754: * @param ast utility object used to create AST nodes
0755: * @param newMethod the <code>MethodDeclaration</code> for the factory method
0756: */
0757: private void createFactoryMethodSignature(AST ast,
0758: MethodDeclaration newMethod) {
0759: List argDecls = newMethod.parameters();
0760:
0761: for (int i = 0; i < fArgTypes.length; i++) {
0762: SingleVariableDeclaration argDecl = ast
0763: .newSingleVariableDeclaration();
0764: Type argType;
0765:
0766: if (i == (fArgTypes.length - 1) && fCtorIsVarArgs) {
0767: // The trailing varargs arg has an extra array dimension, compared to
0768: // what we need to pass to setType()...
0769: argType = typeNodeForTypeBinding(fArgTypes[i]
0770: .getElementType(),
0771: fArgTypes[i].getDimensions() - 1, ast);
0772: argDecl.setVarargs(true);
0773: } else
0774: argType = typeNodeForTypeBinding(fArgTypes[i], 0, ast);
0775:
0776: argDecl.setName(ast.newSimpleName(fFormalArgNames[i]));
0777: argDecl.setType(argType);
0778: argDecls.add(argDecl);
0779: }
0780:
0781: ITypeBinding[] ctorExcepts = fCtorBinding.getExceptionTypes();
0782: List exceptions = newMethod.thrownExceptions();
0783:
0784: for (int i = 0; i < ctorExcepts.length; i++) {
0785: String excName = fImportRewriter.addImport(ctorExcepts[i]);
0786:
0787: exceptions.add(ASTNodeFactory.newName(ast, excName));
0788: }
0789:
0790: copyTypeParameters(ast, newMethod);
0791: }
0792:
0793: /**
0794: * Copies the constructor's parent type's type parameters, if any, as
0795: * method type parameters of the new static factory method. (Recall
0796: * that static methods can't refer to type arguments of the enclosing
0797: * class, since they have no instance to serve as a context.)<br>
0798: * Makes sure to copy the bounds from the owning type, to ensure that the
0799: * return type of the factory method satisfies the bounds of the type
0800: * being instantiated.<br>
0801: * E.g., for ctor Foo() in the type Foo<T extends Number>, be sure that
0802: * the factory method is declared as<br>
0803: * <code>static <T extends Number> Foo<T> createFoo()</code><br>
0804: * and not simply<br>
0805: * <code>static <T> Foo<T> createFoo()</code><br>
0806: * or the compiler will bark.
0807: * @param ast utility object needed to create ASTNode's for the new method
0808: * @param newMethod the method onto which to copy the type parameters
0809: */
0810: private void copyTypeParameters(AST ast, MethodDeclaration newMethod) {
0811: ITypeBinding[] ctorOwnerTypeParms = fCtorBinding
0812: .getDeclaringClass().getTypeParameters();
0813: List/*<TypeParameter>*/factoryMethodTypeParms = newMethod
0814: .typeParameters();
0815: for (int i = 0; i < ctorOwnerTypeParms.length; i++) {
0816: TypeParameter newParm = ast.newTypeParameter();
0817: ITypeBinding[] parmTypeBounds = ctorOwnerTypeParms[i]
0818: .getTypeBounds();
0819: List/*<Type>*/newParmBounds = newParm.typeBounds();
0820:
0821: newParm.setName(ast.newSimpleName(ctorOwnerTypeParms[i]
0822: .getName()));
0823: for (int b = 0; b < parmTypeBounds.length; b++) {
0824: if (parmTypeBounds[b].isClass()
0825: && parmTypeBounds[b].getSuperclass() == null)
0826: continue;
0827:
0828: Type newBound = fImportRewriter.addImport(
0829: parmTypeBounds[b], ast);
0830:
0831: newParmBounds.add(newBound);
0832: }
0833: factoryMethodTypeParms.add(newParm);
0834: }
0835: }
0836:
0837: /**
0838: * @param argType
0839: * @param extraDims number of extra array dimensions to add to the resulting type
0840: * @param ast
0841: * @return a Type that describes the given ITypeBinding. If the binding
0842: * refers to an object type, use the import rewriter to determine whether
0843: * the reference requires a new import, or instead needs to be qualified.<br>
0844: * Like ASTNodeFactory.newType(), but for the handling of imports.
0845: */
0846: private Type typeNodeForTypeBinding(ITypeBinding argType,
0847: int extraDims, AST ast) {
0848: if (extraDims > 0) {
0849: return ast.newArrayType(typeNodeForTypeBinding(argType, 0,
0850: ast), extraDims);
0851:
0852: } else if (argType.isArray()) {
0853: Type elementType = typeNodeForTypeBinding(argType
0854: .getElementType(), extraDims, ast);
0855: return ast.newArrayType(elementType, argType
0856: .getDimensions());
0857:
0858: } else {
0859: return fImportRewriter.addImport(argType, ast);
0860: }
0861: }
0862:
0863: /**
0864: * Create the list of actual arguments to the constructor call that is
0865: * encapsulated inside the factory method, and associate the arguments
0866: * with the given constructor call object.
0867: * @param ast utility object used to create AST nodes
0868: * @param newCtorCall the newly-generated constructor call to be wrapped inside
0869: * the factory method
0870: */
0871: private void createFactoryMethodConstructorArgs(AST ast,
0872: ClassInstanceCreation newCtorCall) {
0873: List argList = newCtorCall.arguments();
0874:
0875: for (int i = 0; i < fArgTypes.length; i++) {
0876: ASTNode ctorArg = ast.newSimpleName(fFormalArgNames[i]);
0877:
0878: argList.add(ctorArg);
0879: }
0880: }
0881:
0882: /**
0883: * Updates the constructor call.
0884: *
0885: * @param ctorCall the ClassInstanceCreation to be marked as replaced
0886: * @param unitRewriter the AST rewriter
0887: * @param gd the edit group to use
0888: */
0889: private void rewriteFactoryMethodCall(
0890: ClassInstanceCreation ctorCall, ASTRewrite unitRewriter,
0891: TextEditGroup gd) {
0892: AST ast = unitRewriter.getAST();
0893: MethodInvocation factoryMethodCall = ast.newMethodInvocation();
0894:
0895: List actualFactoryArgs = factoryMethodCall.arguments();
0896: List actualCtorArgs = ctorCall.arguments();
0897:
0898: // Need to use a qualified name for the factory method if we're not
0899: // in the context of the class holding the factory.
0900: AbstractTypeDeclaration callOwner = (AbstractTypeDeclaration) ASTNodes
0901: .getParent(ctorCall, AbstractTypeDeclaration.class);
0902: ITypeBinding callOwnerBinding = callOwner.resolveBinding();
0903:
0904: if (callOwnerBinding == null
0905: || !Bindings.equals(callOwner.resolveBinding(),
0906: fFactoryOwningClass.resolveBinding())) {
0907: String qualifier = fImportRewriter
0908: .addImport(fFactoryOwningClass.resolveBinding());
0909: factoryMethodCall.setExpression(ASTNodeFactory.newName(ast,
0910: qualifier));
0911: }
0912:
0913: factoryMethodCall.setName(ast.newSimpleName(fNewMethodName));
0914:
0915: for (int i = 0; i < actualCtorArgs.size(); i++) {
0916: Expression actualCtorArg = (Expression) actualCtorArgs
0917: .get(i);
0918: ASTNode movedArg = unitRewriter
0919: .createMoveTarget(actualCtorArg);
0920: actualFactoryArgs.add(movedArg);
0921: }
0922:
0923: unitRewriter.replace(ctorCall, factoryMethodCall, gd);
0924: }
0925:
0926: /**
0927: * @param unit
0928: * @return true iff the given <code>ICompilationUnit</code> is the unit
0929: * containing the original constructor
0930: */
0931: private boolean isConstructorUnit(ICompilationUnit unit) {
0932: return unit.equals(ASTCreator.getCu(fCtorOwningClass));
0933: }
0934:
0935: /**
0936: * @return true iff we should actually change the original constructor's
0937: * visibility to <code>protected</code>. This takes into account the user-
0938: * requested mode and whether the constructor's compilation unit is in
0939: * source form.
0940: */
0941: private boolean shouldProtectConstructor() {
0942: return fProtectConstructor && fCtorOwningClass != null;
0943: }
0944:
0945: /**
0946: * Creates and adds the necessary change to make the constructor method protected.
0947: * @param unitAST
0948: * @param unitRewriter
0949: * @param declGD
0950: * @return false iff the constructor didn't exist (i.e. was implicit)
0951: */
0952: private boolean protectConstructor(CompilationUnit unitAST,
0953: ASTRewrite unitRewriter, TextEditGroup declGD) {
0954: MethodDeclaration constructor = (MethodDeclaration) unitAST
0955: .findDeclaringNode(fCtorBinding.getKey());
0956:
0957: // No need to rewrite the modifiers if the visibility is what we already want it to be.
0958: if (constructor == null
0959: || (JdtFlags.getVisibilityCode(constructor)) == fConstructorVisibility)
0960: return false;
0961: ModifierRewrite.create(unitRewriter, constructor)
0962: .setVisibility(fConstructorVisibility, declGD);
0963: return true;
0964: }
0965:
0966: /**
0967: * Add all changes necessary on the <code>ICompilationUnit</code> in the given
0968: * <code>SearchResultGroup</code> to implement the refactoring transformation
0969: * to the given <code>CompilationUnitChange</code>.
0970: * @param rg the <code>SearchResultGroup</code> for which changes should be created
0971: * @param unitHandle
0972: * @param unitChange the CompilationUnitChange object for the compilation unit in question
0973: * @return <code>true</code> iff a change has been added
0974: * @throws CoreException
0975: */
0976: private boolean addAllChangesFor(SearchResultGroup rg,
0977: ICompilationUnit unitHandle,
0978: CompilationUnitChange unitChange) throws CoreException {
0979: // ICompilationUnit unitHandle= rg.getCompilationUnit();
0980: Assert.isTrue(rg == null
0981: || rg.getCompilationUnit() == unitHandle);
0982: CompilationUnit unit = getASTFor(unitHandle);
0983: ASTRewrite unitRewriter = ASTRewrite.create(unit.getAST());
0984: MultiTextEdit root = new MultiTextEdit();
0985: boolean someChange = false;
0986:
0987: unitChange.setEdit(root);
0988: fImportRewriter = StubUtility.createImportRewrite(unit, true);
0989:
0990: // First create the factory method
0991: if (unitHandle.equals(fFactoryUnitHandle)) {
0992: TextEditGroup factoryGD = new TextEditGroup(
0993: RefactoringCoreMessages.IntroduceFactory_addFactoryMethod);
0994:
0995: createFactoryChange(unitRewriter, unit, factoryGD);
0996: unitChange.addTextEditGroup(factoryGD);
0997: someChange = true;
0998: }
0999:
1000: // Now rewrite all the constructor calls to use the factory method
1001: if (rg != null)
1002: if (replaceConstructorCalls(rg, unit, unitRewriter,
1003: unitChange))
1004: someChange = true;
1005:
1006: // Finally, make the constructor private, if requested.
1007: if (shouldProtectConstructor() && isConstructorUnit(unitHandle)) {
1008: TextEditGroup declGD = new TextEditGroup(
1009: RefactoringCoreMessages.IntroduceFactory_protectConstructor);
1010:
1011: if (protectConstructor(unit, unitRewriter, declGD)) {
1012: unitChange.addTextEditGroup(declGD);
1013: someChange = true;
1014: }
1015: }
1016:
1017: if (someChange) {
1018: root.addChild(unitRewriter.rewriteAST());
1019: root.addChild(fImportRewriter.rewriteImports(null));
1020: }
1021:
1022: return someChange;
1023: }
1024:
1025: /**
1026: * @param unitHandle
1027: * @return an AST for the given compilation unit handle.<br>
1028: * If this is the unit containing the selection or the unit in which the factory
1029: * is to reside, checks the appropriate field (<code>fCU</code> or <code>fFactoryCU</code>,
1030: * respectively) and initializes the field with a new AST only if not already done.
1031: */
1032: private CompilationUnit getASTFor(ICompilationUnit unitHandle) {
1033: if (unitHandle.equals(fCUHandle)) { // is this the unit containing the selection?
1034: if (fCU == null) {
1035: fCU = ASTCreator.createAST(unitHandle, null);
1036: if (fCU.equals(fFactoryUnitHandle)) // if selection unit and factory unit are the same...
1037: fFactoryCU = fCU; // ...make sure the factory unit gets initialized
1038: }
1039: return fCU;
1040: } else if (unitHandle.equals(fFactoryUnitHandle)) { // is this the "factory unit"?
1041: if (fFactoryCU == null)
1042: fFactoryCU = ASTCreator.createAST(unitHandle, null);
1043: return fFactoryCU;
1044: } else
1045: return ASTCreator.createAST(unitHandle, null);
1046: }
1047:
1048: /**
1049: * Use the given <code>ASTRewrite</code> to replace direct calls to the constructor
1050: * with calls to the newly-created factory method.
1051: * @param rg the <code>SearchResultGroup</code> indicating all of the constructor references
1052: * @param unit the <code>CompilationUnit</code> to be rewritten
1053: * @param unitRewriter the rewriter
1054: * @param unitChange the compilation unit change
1055: * @throws CoreException
1056: * @return true iff at least one constructor call site was rewritten.
1057: */
1058: private boolean replaceConstructorCalls(SearchResultGroup rg,
1059: CompilationUnit unit, ASTRewrite unitRewriter,
1060: CompilationUnitChange unitChange) throws CoreException {
1061: Assert.isTrue(ASTCreator.getCu(unit).equals(
1062: rg.getCompilationUnit()));
1063: SearchMatch[] hits = rg.getSearchResults();
1064: boolean someCallPatched = false;
1065:
1066: for (int i = 0; i < hits.length; i++) {
1067: ASTNode ctrCall = getCtorCallAt(hits[i].getOffset(),
1068: hits[i].getLength(), unit);
1069:
1070: if (ctrCall instanceof ClassInstanceCreation) {
1071: TextEditGroup gd = new TextEditGroup(
1072: RefactoringCoreMessages.IntroduceFactory_replaceCalls);
1073:
1074: rewriteFactoryMethodCall(
1075: (ClassInstanceCreation) ctrCall, unitRewriter,
1076: gd);
1077: unitChange.addTextEditGroup(gd);
1078: someCallPatched = true;
1079: } else if (ctrCall instanceof MethodRef) {
1080: TextEditGroup gd = new TextEditGroup(
1081: RefactoringCoreMessages.IntroduceFactoryRefactoring_replaceJavadocReference);
1082:
1083: rewriteJavadocReference((MethodRef) ctrCall,
1084: unitRewriter, gd);
1085: unitChange.addTextEditGroup(gd);
1086: someCallPatched = true;
1087: }
1088: }
1089: return someCallPatched;
1090: }
1091:
1092: private void rewriteJavadocReference(MethodRef javadocRef,
1093: ASTRewrite unitRewriter, TextEditGroup gd) {
1094: AST ast = unitRewriter.getAST();
1095: unitRewriter.replace(javadocRef.getName(), ast
1096: .newSimpleName(fNewMethodName), gd);
1097: }
1098:
1099: /**
1100: * Look "in the vicinity" of the given range to find the <code>ClassInstanceCreation</code>
1101: * node that this search hit identified. Necessary because the <code>SearchEngine</code>
1102: * doesn't always cough up text extents that <code>NodeFinder.perform()</code> agrees with.
1103: * @param start
1104: * @param length
1105: * @param unitAST
1106: * @return return a {@link ClassInstanceCreation} or a {@link MethodRef} or <code>null</code> if this is really a constructor->constructor call (e.g. "this(...)")
1107: * @throws CoreException
1108: */
1109: private ASTNode getCtorCallAt(int start, int length,
1110: CompilationUnit unitAST) throws CoreException {
1111: ICompilationUnit unitHandle = ASTCreator.getCu(unitAST);
1112: ASTNode node = NodeFinder.perform(unitAST, start, length);
1113:
1114: if (node == null)
1115: throw new CoreException(
1116: JavaUIStatus
1117: .createError(
1118: IStatus.ERROR,
1119: Messages
1120: .format(
1121: RefactoringCoreMessages.IntroduceFactory_noASTNodeForConstructorSearchHit,
1122: new Object[] {
1123: Integer
1124: .toString(start),
1125: Integer
1126: .toString(start
1127: + length),
1128: unitHandle
1129: .getSource()
1130: .substring(
1131: start,
1132: start
1133: + length),
1134: unitHandle
1135: .getElementName() }),
1136: null));
1137:
1138: if (node instanceof ClassInstanceCreation) {
1139: return node;
1140: } else if (node instanceof VariableDeclaration) {
1141: Expression init = ((VariableDeclaration) node)
1142: .getInitializer();
1143:
1144: if (init instanceof ClassInstanceCreation) {
1145: return init;
1146: } else if (init != null)
1147: throw new CoreException(
1148: JavaUIStatus
1149: .createError(
1150: IStatus.ERROR,
1151: Messages
1152: .format(
1153: RefactoringCoreMessages.IntroduceFactory_unexpectedInitializerNodeType,
1154: new Object[] {
1155: init
1156: .toString(),
1157: unitHandle
1158: .getElementName() }),
1159: null));
1160: else
1161: throw new CoreException(
1162: JavaUIStatus
1163: .createError(
1164: IStatus.ERROR,
1165: Messages
1166: .format(
1167: RefactoringCoreMessages.IntroduceFactory_noConstructorCallNodeInsideFoundVarbleDecl,
1168: new Object[] { node
1169: .toString() }),
1170: null));
1171: } else if (node instanceof ConstructorInvocation) {
1172: // This is a call we can bypass; it's from one constructor flavor
1173: // to another flavor on the same class.
1174: return null;
1175: } else if (node instanceof SuperConstructorInvocation) {
1176: // This is a call we can bypass; it's from one constructor flavor
1177: // to another flavor on the same class.
1178: fConstructorVisibility = Modifier.PROTECTED;
1179: return null;
1180: } else if (node instanceof ExpressionStatement) {
1181: Expression expr = ((ExpressionStatement) node)
1182: .getExpression();
1183:
1184: if (expr instanceof ClassInstanceCreation)
1185: return expr;
1186: else
1187: throw new CoreException(
1188: JavaUIStatus
1189: .createError(
1190: IStatus.ERROR,
1191: Messages
1192: .format(
1193: RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit,
1194: new Object[] {
1195: expr
1196: .toString(),
1197: unitHandle
1198: .getElementName() }),
1199: null));
1200: } else if (node instanceof SimpleName
1201: && (node.getParent() instanceof MethodDeclaration || node
1202: .getParent() instanceof AbstractTypeDeclaration)) {
1203: // We seem to have been given a hit for an implicit call to the base-class constructor.
1204: // Do nothing with this (implicit) call, but have to make sure we make the derived class
1205: // doesn't lose access to the base-class constructor (so make it 'protected', not 'private').
1206: fConstructorVisibility = Modifier.PROTECTED;
1207: return null;
1208: } else if (node instanceof MethodRef) {
1209: return node;
1210: } else
1211: throw new CoreException(
1212: JavaUIStatus
1213: .createError(
1214: IStatus.ERROR,
1215: Messages
1216: .format(
1217: RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit,
1218: new Object[] {
1219: node
1220: .getClass()
1221: .getName()
1222: + "('" + node.toString() + "')", unitHandle.getElementName() }), //$NON-NLS-1$ //$NON-NLS-2$
1223: null));
1224: }
1225:
1226: /**
1227: * Perform the AST rewriting necessary on the given <code>CompilationUnit</code>
1228: * to create the factory method. The method will reside on the type identified by
1229: * <code>fFactoryOwningClass</code>.
1230: * @param unitRewriter
1231: * @param unit
1232: * @param gd the <code>GroupDescription</code> to associate with the changes made
1233: */
1234: private void createFactoryChange(ASTRewrite unitRewriter,
1235: CompilationUnit unit, TextEditGroup gd) {
1236: // ================================================================================
1237: // First add the factory itself (method, class, and interface as needed/directed by user)
1238: AST ast = unit.getAST();
1239:
1240: fFactoryMethod = createFactoryMethod(ast, fCtorBinding,
1241: unitRewriter);
1242:
1243: AbstractTypeDeclaration factoryOwner = (AbstractTypeDeclaration) unit
1244: .findDeclaringNode(fFactoryOwningClass.resolveBinding()
1245: .getKey());
1246: fImportRewriter.addImport(fCtorOwningClass.resolveBinding());
1247:
1248: int idx = ASTNodes.getInsertionIndex(fFactoryMethod,
1249: factoryOwner.bodyDeclarations());
1250:
1251: if (idx < 0)
1252: idx = 0; // Guard against bug in getInsertionIndex()
1253: unitRewriter.getListRewrite(factoryOwner,
1254: factoryOwner.getBodyDeclarationsProperty()).insertAt(
1255: fFactoryMethod, idx, gd);
1256: }
1257:
1258: /* (non-Javadoc)
1259: * @see org.eclipse.ltk.core.refactoring.Refactoring#createChange(org.eclipse.core.runtime.IProgressMonitor)
1260: */
1261: public Change createChange(IProgressMonitor pm)
1262: throws CoreException {
1263: try {
1264: pm
1265: .beginTask(
1266: RefactoringCoreMessages.IntroduceFactory_createChanges,
1267: fAllCallsTo.length);
1268: final ITypeBinding binding = fFactoryOwningClass
1269: .resolveBinding();
1270: final Map arguments = new HashMap();
1271: String project = null;
1272: IJavaProject javaProject = fCUHandle.getJavaProject();
1273: if (javaProject != null)
1274: project = javaProject.getElementName();
1275: int flags = JavaRefactoringDescriptor.JAR_MIGRATION
1276: | JavaRefactoringDescriptor.JAR_REFACTORING
1277: | RefactoringDescriptor.STRUCTURAL_CHANGE
1278: | RefactoringDescriptor.MULTI_CHANGE;
1279: if (binding.isNested() && !binding.isMember())
1280: flags |= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
1281: final String description = Messages
1282: .format(
1283: RefactoringCoreMessages.IntroduceFactoryRefactoring_descriptor_description_short,
1284: fCtorOwningClass.getName());
1285: final String header = Messages
1286: .format(
1287: RefactoringCoreMessages.IntroduceFactory_descriptor_description,
1288: new String[] {
1289: fNewMethodName,
1290: BindingLabelProvider
1291: .getBindingLabel(
1292: binding,
1293: JavaElementLabels.ALL_FULLY_QUALIFIED),
1294: BindingLabelProvider
1295: .getBindingLabel(
1296: fCtorBinding,
1297: JavaElementLabels.ALL_FULLY_QUALIFIED) });
1298: final JDTRefactoringDescriptorComment comment = new JDTRefactoringDescriptorComment(
1299: project, this , header);
1300: comment
1301: .addSetting(Messages
1302: .format(
1303: RefactoringCoreMessages.IntroduceFactoryRefactoring_original_pattern,
1304: BindingLabelProvider
1305: .getBindingLabel(
1306: fCtorBinding,
1307: JavaElementLabels.ALL_FULLY_QUALIFIED)));
1308: comment
1309: .addSetting(Messages
1310: .format(
1311: RefactoringCoreMessages.IntroduceFactoryRefactoring_factory_pattern,
1312: fNewMethodName));
1313: comment
1314: .addSetting(Messages
1315: .format(
1316: RefactoringCoreMessages.IntroduceFactoryRefactoring_owner_pattern,
1317: BindingLabelProvider
1318: .getBindingLabel(
1319: binding,
1320: JavaElementLabels.ALL_FULLY_QUALIFIED)));
1321: if (fProtectConstructor)
1322: comment
1323: .addSetting(RefactoringCoreMessages.IntroduceFactoryRefactoring_declare_private);
1324: final IntroduceFactoryDescriptor descriptor = new IntroduceFactoryDescriptor(
1325: project, description, comment.asString(),
1326: arguments, flags);
1327: arguments.put(
1328: JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT,
1329: JavaRefactoringDescriptorUtil.elementToHandle(
1330: project, fCUHandle));
1331: arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME,
1332: fNewMethodName);
1333: arguments
1334: .put(
1335: JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1,
1336: JavaRefactoringDescriptorUtil
1337: .elementToHandle(project, binding
1338: .getJavaElement()));
1339: arguments
1340: .put(
1341: JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION,
1342: new Integer(fSelectionStart).toString()
1343: + " " + new Integer(fSelectionLength).toString()); //$NON-NLS-1$
1344: arguments.put(ATTRIBUTE_PROTECT, Boolean.valueOf(
1345: fProtectConstructor).toString());
1346: final DynamicValidationStateChange result = new DynamicValidationRefactoringChange(
1347: descriptor,
1348: RefactoringCoreMessages.IntroduceFactory_name);
1349: boolean hitInFactoryClass = false;
1350: boolean hitInCtorClass = false;
1351: for (int i = 0; i < fAllCallsTo.length; i++) {
1352: SearchResultGroup rg = fAllCallsTo[i];
1353: ICompilationUnit unitHandle = rg.getCompilationUnit();
1354: CompilationUnitChange cuChange = new CompilationUnitChange(
1355: getName(), unitHandle);
1356:
1357: if (addAllChangesFor(rg, unitHandle, cuChange))
1358: result.add(cuChange);
1359:
1360: if (unitHandle.equals(fFactoryUnitHandle))
1361: hitInFactoryClass = true;
1362: if (unitHandle.equals(ASTCreator
1363: .getCu(fCtorOwningClass)))
1364: hitInCtorClass = true;
1365:
1366: pm.worked(1);
1367: if (pm.isCanceled())
1368: throw new OperationCanceledException();
1369: }
1370: if (!hitInFactoryClass) { // Handle factory class if no search hits there
1371: CompilationUnitChange cuChange = new CompilationUnitChange(
1372: getName(), fFactoryUnitHandle);
1373: addAllChangesFor(null, fFactoryUnitHandle, cuChange);
1374: result.add(cuChange);
1375: }
1376: if (!hitInCtorClass
1377: && !fFactoryUnitHandle.equals(ASTCreator
1378: .getCu(fCtorOwningClass))) { // Handle constructor-owning class if no search hits there
1379: CompilationUnitChange cuChange = new CompilationUnitChange(
1380: getName(), ASTCreator.getCu(fCtorOwningClass));
1381: addAllChangesFor(null, ASTCreator
1382: .getCu(fCtorOwningClass), cuChange);
1383: result.add(cuChange);
1384: }
1385: return result;
1386: } finally {
1387: pm.done();
1388: }
1389: }
1390:
1391: /* (non-Javadoc)
1392: * @see org.eclipse.ltk.core.refactoring.Refactoring#getName()
1393: */
1394: public String getName() {
1395: return RefactoringCoreMessages.IntroduceFactory_name;
1396: }
1397:
1398: /**
1399: * Returns the name to be used for the generated factory method.
1400: * @return the new method name
1401: */
1402: public String getNewMethodName() {
1403: return fNewMethodName;
1404: }
1405:
1406: /**
1407: * Sets the name to be used for the generated factory method.<br>
1408: * Returns a <code>RefactoringStatus</code> that indicates whether the
1409: * given name is valid for the new factory method.
1410: * @param newMethodName the name to be used for the generated factory method
1411: * @return the resulting status
1412: */
1413: public RefactoringStatus setNewMethodName(String newMethodName) {
1414: Assert.isNotNull(newMethodName);
1415: fNewMethodName = newMethodName;
1416:
1417: RefactoringStatus stat = Checks.checkMethodName(newMethodName,
1418: fCUHandle);
1419:
1420: stat.merge(isUniqueMethodName(newMethodName));
1421:
1422: return stat;
1423: }
1424:
1425: /**
1426: * @param methodName
1427: * @return a <code>RefactoringStatus</code> that identifies whether the
1428: * the name <code>newMethodName</code> is available to use as the name of
1429: * the new factory method within the factory-owner class (either a to-be-
1430: * created factory class or the constructor-owning class, depending on the
1431: * user options).
1432: */
1433: private RefactoringStatus isUniqueMethodName(String methodName) {
1434: ITypeBinding declaringClass = fCtorBinding.getDeclaringClass();
1435: if (Bindings.findMethodInType(declaringClass, methodName,
1436: fCtorBinding.getParameterTypes()) != null) {
1437: String format = Messages
1438: .format(
1439: RefactoringCoreMessages.IntroduceFactory_duplicateMethodName,
1440: methodName);
1441: return RefactoringStatus.createErrorStatus(format);
1442: }
1443: return new RefactoringStatus();
1444: }
1445:
1446: /**
1447: * Returns true iff the selected constructor can be protected.
1448: * @return return <code>true</code> if the constructor can be made protected
1449: */
1450: public boolean canProtectConstructor() {
1451: return !fCtorBinding.isSynthetic()
1452: && fFactoryCU.findDeclaringNode(fCtorBinding.getKey()) != null;
1453: }
1454:
1455: /**
1456: * If the argument is true, change the visibility of the constructor to
1457: * <code>protected</code>, thereby encapsulating it.
1458: * @param protectConstructor
1459: */
1460: public void setProtectConstructor(boolean protectConstructor) {
1461: fProtectConstructor = protectConstructor;
1462: }
1463:
1464: /**
1465: * Returns the project on behalf of which this refactoring was invoked.
1466: * @return returns the Java project
1467: */
1468: public IJavaProject getProject() {
1469: return fCUHandle.getJavaProject();
1470: }
1471:
1472: /**
1473: * Sets the class on which the generated factory method is to be placed.
1474: * @param fullyQualifiedTypeName an <code>IType</code> referring to an existing class
1475: * @return return the resulting status
1476: */
1477: public RefactoringStatus setFactoryClass(
1478: String fullyQualifiedTypeName) {
1479: IType factoryType;
1480:
1481: try {
1482: factoryType = findFactoryClass(fullyQualifiedTypeName);
1483: if (factoryType == null)
1484: return RefactoringStatus
1485: .createErrorStatus(Messages
1486: .format(
1487: RefactoringCoreMessages.IntroduceFactory_noSuchClass,
1488: fullyQualifiedTypeName));
1489:
1490: if (factoryType.isAnnotation())
1491: return RefactoringStatus
1492: .createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryMethodOnAnnotation);
1493: if (factoryType.isInterface())
1494: return RefactoringStatus
1495: .createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryMethodOnInterface);
1496: } catch (JavaModelException e) {
1497: return RefactoringStatus
1498: .createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantCheckForInterface);
1499: }
1500:
1501: ICompilationUnit factoryUnitHandle = factoryType
1502: .getCompilationUnit();
1503:
1504: if (factoryType.isBinary())
1505: return RefactoringStatus
1506: .createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryInBinaryClass);
1507: else {
1508: try {
1509: if (!fFactoryUnitHandle.equals(factoryUnitHandle)) {
1510: fFactoryCU = getASTFor(factoryUnitHandle);
1511: fFactoryUnitHandle = factoryUnitHandle;
1512: }
1513: fFactoryOwningClass = (AbstractTypeDeclaration) ASTNodes
1514: .getParent(NodeFinder.perform(fFactoryCU,
1515: factoryType.getNameRange()),
1516: AbstractTypeDeclaration.class);
1517:
1518: String factoryPkg = factoryType.getPackageFragment()
1519: .getElementName();
1520: String ctorPkg = fCtorOwningClass.resolveBinding()
1521: .getPackage().getName();
1522:
1523: if (!factoryPkg.equals(ctorPkg))
1524: fConstructorVisibility = Modifier.PUBLIC;
1525: else if (fFactoryOwningClass != fCtorOwningClass)
1526: fConstructorVisibility = 0; // No such thing as Modifier.PACKAGE...
1527:
1528: if (fFactoryOwningClass != fCtorOwningClass)
1529: fConstructorVisibility = 0; // No such thing as Modifier.PACKAGE...
1530:
1531: } catch (JavaModelException e) {
1532: return RefactoringStatus.createFatalErrorStatus(e
1533: .getMessage());
1534: }
1535: return new RefactoringStatus();
1536: }
1537: }
1538:
1539: /**
1540: * Finds the factory class associated with the fully qualified name.
1541: * @param fullyQualifiedTypeName the fully qualified type name
1542: * @return the factory class, or <code>null</code> if not found
1543: * @throws JavaModelException if an error occurs while finding the factory class
1544: */
1545: private IType findFactoryClass(String fullyQualifiedTypeName)
1546: throws JavaModelException {
1547: IType factoryType = getProject().findType(
1548: fullyQualifiedTypeName);
1549: if (factoryType == null) // presumably a non-primary type; try the search engine
1550: factoryType = findNonPrimaryType(fullyQualifiedTypeName,
1551: new NullProgressMonitor(), new RefactoringStatus());
1552: return factoryType;
1553: }
1554:
1555: /**
1556: * Returns the name of the class on which the generated factory method is
1557: * to be placed.
1558: * @return return the factory class name
1559: */
1560: public String getFactoryClassName() {
1561: return fFactoryOwningClass.resolveBinding().getQualifiedName();
1562: }
1563:
1564: public RefactoringStatus initialize(
1565: final RefactoringArguments arguments) {
1566: if (arguments instanceof JavaRefactoringArguments) {
1567: final JavaRefactoringArguments extended = (JavaRefactoringArguments) arguments;
1568: final String selection = extended
1569: .getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION);
1570: if (selection != null) {
1571: int offset = -1;
1572: int length = -1;
1573: final StringTokenizer tokenizer = new StringTokenizer(
1574: selection);
1575: if (tokenizer.hasMoreTokens())
1576: offset = Integer.valueOf(tokenizer.nextToken())
1577: .intValue();
1578: if (tokenizer.hasMoreTokens())
1579: length = Integer.valueOf(tokenizer.nextToken())
1580: .intValue();
1581: if (offset >= 0 && length >= 0) {
1582: fSelectionStart = offset;
1583: fSelectionLength = length;
1584: } else
1585: return RefactoringStatus
1586: .createFatalErrorStatus(Messages
1587: .format(
1588: RefactoringCoreMessages.InitializableRefactoring_illegal_argument,
1589: new Object[] {
1590: selection,
1591: JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION }));
1592: } else
1593: return RefactoringStatus
1594: .createFatalErrorStatus(Messages
1595: .format(
1596: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1597: JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION));
1598: String handle = extended
1599: .getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT);
1600: if (handle != null) {
1601: final IJavaElement element = JavaRefactoringDescriptorUtil
1602: .handleToElement(extended.getProject(), handle,
1603: false);
1604: if (element == null
1605: || !element.exists()
1606: || element.getElementType() != IJavaElement.COMPILATION_UNIT)
1607: return createInputFatalStatus(element,
1608: IJavaRefactorings.INTRODUCE_FACTORY);
1609: else {
1610: fCUHandle = (ICompilationUnit) element;
1611: initialize();
1612: }
1613: } else
1614: return RefactoringStatus
1615: .createFatalErrorStatus(Messages
1616: .format(
1617: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1618: JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
1619: handle = extended
1620: .getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1);
1621: if (handle != null) {
1622: final IJavaElement element = JavaRefactoringDescriptorUtil
1623: .handleToElement(extended.getProject(), handle,
1624: false);
1625: if (element == null
1626: || !element.exists()
1627: || element.getElementType() != IJavaElement.TYPE)
1628: return createInputFatalStatus(element,
1629: IJavaRefactorings.INTRODUCE_FACTORY);
1630: else {
1631: final IType type = (IType) element;
1632: fFactoryClassName = type.getFullyQualifiedName();
1633: }
1634: } else
1635: return RefactoringStatus
1636: .createFatalErrorStatus(Messages
1637: .format(
1638: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1639: JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
1640: final String name = extended
1641: .getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME);
1642: if (name != null && !"".equals(name)) //$NON-NLS-1$
1643: fNewMethodName = name;
1644: else
1645: return RefactoringStatus
1646: .createFatalErrorStatus(Messages
1647: .format(
1648: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1649: JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME));
1650: final String protect = extended
1651: .getAttribute(ATTRIBUTE_PROTECT);
1652: if (protect != null) {
1653: fProtectConstructor = Boolean.valueOf(protect)
1654: .booleanValue();
1655: } else
1656: return RefactoringStatus
1657: .createFatalErrorStatus(Messages
1658: .format(
1659: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1660: ATTRIBUTE_PROTECT));
1661: } else
1662: return RefactoringStatus
1663: .createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
1664: return new RefactoringStatus();
1665: }
1666: }
|