/*******************************************************************************
 * Copyright (c) 2000, 2020 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Samrat Dhillon <samrat.dhillon@gmail.com> - [introduce factory] Introduce Factory on an abstract class adds a statement to create an instance of that class - https://bugs.eclipse.org/bugs/show_bug.cgi?id=395016
 *     Stephan Herrmann - Contribution for Bug 463360 - [override method][null] generating method override should not create redundant null annotations
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.code;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;

import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEditGroup;

import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.MethodRef;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
import org.eclipse.jdt.core.refactoring.descriptors.IntroduceFactoryDescriptor;
import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;

import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory;
import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2Core;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.BodyDeclarationRewrite;
import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine2;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationStateChange;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.ASTCreator;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.MethodsSourcePositionComparator;
import org.eclipse.jdt.internal.corext.util.SearchUtils;

import org.eclipse.jdt.ui.JavaElementLabels;

import org.eclipse.jdt.internal.ui.JavaUIStatus;
import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings;
import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider;

/**
 * Refactoring class that permits the substitution of a factory method
 * for direct calls to a given constructor.
 * @author rfuhrer
 */
public class IntroduceFactoryRefactoring extends Refactoring {

	private static final String ATTRIBUTE_PROTECT= "protect"; //$NON-NLS-1$

	/**
	 * The handle for the compilation unit holding the selection that was
	 * passed into this refactoring.
	 */
	private ICompilationUnit fCUHandle;

	/**
	 * The AST for the compilation unit holding the selection that was
	 * passed into this refactoring.
	 */
	private CompilationUnit fCU;

	/**
	 * Handle for compilation unit in which the factory method/class/interface will be
	 * generated.
	 */
	private ICompilationUnit fFactoryUnitHandle;

	/**
	 * The start of the original textual selection in effect when this refactoring
	 * was initiated. If the refactoring was initiated from a structured selection
	 * (e.g. from the outline view), then this refers to the textual selection that
	 * corresponds to the structured selection item.
	 */
	private int fSelectionStart;

	/**
	 * The length of the original textual selection in effect when this refactoring
	 * was initiated. If the refactoring was initiated from a structured selection
	 * (e.g. from the outline view), then this refers to the textual selection that
	 * corresponds to the structured selection item.
	 */
	private int fSelectionLength;

	/**
	 * The AST node corresponding to the user's textual selection.
	 */
	private ASTNode fSelectedNode;

	/**
	 * The method binding for the selected constructor.
	 */
	private IMethodBinding fCtorBinding;

	/**
	 * <code>TypeDeclaration</code> for class containing the constructor to be
	 * encapsulated.
	 */
	private AbstractTypeDeclaration fCtorOwningClass;

	/**
	 * The name to be given to the generated factory method.
	 */
	private String fNewMethodName= null;

	/**
	 * An array of <code>SearchResultGroup</code>'s of all call sites
	 * that refer to the constructor signature in question.
	 */
	private SearchResultGroup[] fAllCallsTo;

	/**
	 * The class that will own the factory method/class/interface.
	 */
	private AbstractTypeDeclaration fFactoryOwningClass;

	/**
	 * The newly-generated factory method.
	 */
	private MethodDeclaration fFactoryMethod= null;

	/**
	 * An array containing the names of the constructor's formal arguments,
	 * if available, otherwise "arg1" ... "argN".
	 */
	private String[] fFormalArgNames= null;

	/**
	 * An array of <code>ITypeBinding</code>'s that describes the types of
	 * the constructor arguments, in order.
	 */
	private ITypeBinding[] fArgTypes;

	/**
	 * True iff the given constructor has a varargs signature.
	 */
	private boolean fCtorIsVarArgs;

	/**
	 * If true, change the visibility of the constructor to protected to better
	 * encapsulate it.
	 */
	private boolean fProtectConstructor= true;

	/**
	 * An <code>ImportRewrite</code> that manages imports needed to satisfy
	 * newly-introduced type references in the <code>ICompilationUnit</code>
	 * currently being rewritten during <code>createChange()</code>.
	 */
	private ImportRewrite fImportRewriter;

	/**
	 * True iff there are call sites for the constructor to be encapsulated
	 * located in binary classes.
	 */
	private boolean fCallSitesInBinaryUnits;

	/**
	 * <code>CompilationUnit</code> in which the factory is to be created.
	 */
	private CompilationUnit fFactoryCU;

	/**
	 * The fully qualified name of the factory class. This is only used
	 * if invoked from a refactoring script.
	 */
	private String fFactoryClassName;

	private int fConstructorVisibility= Modifier.PRIVATE;

	/**
	 * Creates a new <code>IntroduceFactoryRefactoring</code> with the given selection
	 * on the given compilation unit.
	 * @param cu the <code>ICompilationUnit</code> in which the user selection was made, or <code>null</code> if invoked from scripting
	 * @param selectionStart the start of the textual selection in <code>cu</code>
	 * @param selectionLength the length of the textual selection in <code>cu</code>
	 */
	public IntroduceFactoryRefactoring(ICompilationUnit cu, int selectionStart, int selectionLength) {
		Assert.isTrue(selectionStart  >= 0);
		Assert.isTrue(selectionLength >= 0);
		fSelectionStart= selectionStart;
		fSelectionLength= selectionLength;
		fCUHandle= cu;
		if (cu != null)
			initialize();
	}

    public IntroduceFactoryRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) {
   		this(null, 0, 0);
   		RefactoringStatus initializeStatus= initialize(arguments);
   		status.merge(initializeStatus);
    }

	private void initialize() {
		fCU= ASTCreator.createAST(fCUHandle, null);
	}

	/**
	 * Finds and returns the <code>ASTNode</code> for the given source text
	 * selection, if it is an entire constructor call or the class name portion
	 * of a constructor call or constructor declaration, or null otherwise.
	 * @param unit The compilation unit in which the selection was made
	 * @param offset The textual offset of the start of the selection
	 * @param length The length of the selection in characters
	 * @return ClassInstanceCreation or MethodDeclaration
	 */
	private ASTNode getTargetNode(ICompilationUnit unit, int offset, int length) {
		ASTNode node= ASTNodes.getNormalizedNode(NodeFinder.perform(fCU, offset, length));
		if (node.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION)
			return node;
		if (node.getNodeType() == ASTNode.METHOD_DECLARATION && ((MethodDeclaration)node).isConstructor())
			return node;
		// we have some sub node. Make sure its the right child of the parent
		StructuralPropertyDescriptor location= node.getLocationInParent();
		ASTNode parent= node.getParent();
		if (location == ClassInstanceCreation.TYPE_PROPERTY
				|| (location == MethodDeclaration.NAME_PROPERTY && ((MethodDeclaration)parent).isConstructor())) {
			return parent;
		}
		return null;
	}

	/**
	 * Determines what kind of AST node was selected, and returns an error status
	 * if the kind of node is inappropriate for this refactoring.
	 * @param pm
	 * @return a RefactoringStatus indicating whether the selection is valid
	 * @throws JavaModelException
	 */
	private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaModelException {
		try {
			pm.beginTask(RefactoringCoreMessages.IntroduceFactory_examiningSelection, 2);

			fSelectedNode= getTargetNode(fCUHandle, fSelectionStart, fSelectionLength);

			if (fSelectedNode == null)
				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_notAConstructorInvocation);

			// getTargetNode() must return either a ClassInstanceCreation or a
			// constructor MethodDeclaration; nothing else.
			if (fSelectedNode instanceof ClassInstanceCreation) {
				ClassInstanceCreation classInstanceCreation= (ClassInstanceCreation)fSelectedNode;
				fCtorBinding= classInstanceCreation.resolveConstructorBinding();
			} else if (fSelectedNode instanceof MethodDeclaration) {
				MethodDeclaration methodDeclaration= (MethodDeclaration)fSelectedNode;
				fCtorBinding= methodDeclaration.resolveBinding();
			}

			if (fCtorBinding == null)
				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unableToResolveConstructorBinding);

			// If this constructor is of a generic type, get the generic version,
			// not some instantiation thereof.
			fCtorBinding= fCtorBinding.getMethodDeclaration();

			pm.worked(1);

			// We don't handle constructors of nested types at the moment
			if (fCtorBinding.getDeclaringClass().isNested())
				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unsupportedNestedTypes);

			ITypeBinding	ctorType= fCtorBinding.getDeclaringClass();
			IType			ctorOwningType= (IType) ctorType.getJavaElement();

			if (ctorOwningType.isBinary())
				// Can't modify binary CU; don't know what CU to put factory method
				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInBinaryClass);
			if (ctorOwningType.isEnum())
				// Doesn't make sense to encapsulate enum constructors
				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInEnum);

			// Put the generated factory method inside the type that owns the constructor
			fFactoryUnitHandle= ctorOwningType.getCompilationUnit();
			fFactoryCU= getASTFor(fFactoryUnitHandle);

			Name	ctorOwnerName= (Name) NodeFinder.perform(fFactoryCU, ctorOwningType.getNameRange());

			fCtorOwningClass= ASTNodes.getParent(ctorOwnerName, AbstractTypeDeclaration.class);
			fFactoryOwningClass= fCtorOwningClass;

			pm.worked(1);

			if (fNewMethodName == null)
				return setNewMethodName("create" + fCtorBinding.getName());//$NON-NLS-1$
			else
				return new RefactoringStatus();
		} finally {
			pm.done();
		}
	}

	/*
	 * @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkActivation(org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
		try {
			pm.beginTask(RefactoringCoreMessages.IntroduceFactory_checkingActivation, 1);

			if (!fCUHandle.isStructureKnown())
				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_syntaxError);

			return checkSelection(new SubProgressMonitor(pm, 1));
		} finally {
			pm.done();
		}
	}

	/**
	 * @param searchHits
	 * @return the set of compilation units that will be affected by this
	 * particular invocation of this refactoring. This in general includes
	 * the class containing the constructor in question, as well as all
	 * call sites to the constructor.
	 */
	private ICompilationUnit[] collectAffectedUnits(SearchResultGroup[] searchHits) {
		Collection<ICompilationUnit>	result= new ArrayList<>();
		boolean hitInFactoryClass= false;

		for (SearchResultGroup rg : searchHits) {
			ICompilationUnit	icu= rg.getCompilationUnit();

			result.add(icu);
			if (icu.equals(fFactoryUnitHandle))
				hitInFactoryClass= true;
		}
		if (!hitInFactoryClass)
			result.add(fFactoryUnitHandle);
		return result.toArray(new ICompilationUnit[result.size()]);
	}

	/**
	 * @param ctor
	 * @param methodBinding
	 * @return a <code>SearchPattern</code> that finds all calls to the constructor
	 * identified by the argument <code>methodBinding</code>.
	 */
	private SearchPattern createSearchPattern(IMethod ctor, IMethodBinding methodBinding) {
		Assert.isNotNull(methodBinding,
				RefactoringCoreMessages.IntroduceFactory_noBindingForSelectedConstructor);

		if (ctor != null)
			return SearchPattern.createPattern(ctor, IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
		else { // perhaps a synthetic method? (but apparently not always... hmmm...)
			// Can't find an IMethod for this method, so build a string pattern instead
			StringBuilder	buf= new StringBuilder();

			buf.append(methodBinding.getDeclaringClass().getQualifiedName())
			   .append("(");//$NON-NLS-1$
			for(int i=0; i < fArgTypes.length; i++) {
				if (i != 0)
					buf.append(","); //$NON-NLS-1$
				buf.append(fArgTypes[i].getQualifiedName());
			}
			buf.append(")"); //$NON-NLS-1$
			return SearchPattern.createPattern(buf.toString(), IJavaSearchConstants.CONSTRUCTOR,
					IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
		}
	}

	private IJavaSearchScope createSearchScope(IMethod ctor, IMethodBinding binding) throws JavaModelException {
		if (ctor != null) {
			return RefactoringScopeFactory.create(ctor);
		} else {
			ITypeBinding type= Bindings.getTopLevelType(binding.getDeclaringClass());
			return RefactoringScopeFactory.create(type.getJavaElement());
		}
	}

	/**
	 * @param groups
	 * @return an array of <code>SearchResultGroup</code>'s like the argument,
	 * but omitting those groups that have no corresponding compilation unit
	 * (i.e. are binary and therefore can't be modified).
	 */
	private SearchResultGroup[] excludeBinaryUnits(SearchResultGroup[] groups) {
		Collection<SearchResultGroup>	result= new ArrayList<>();

		for (SearchResultGroup rg : groups) {
			ICompilationUnit	unit= rg.getCompilationUnit();

			if (unit != null) // ignore hits within a binary unit
				result.add(rg);
			else
				fCallSitesInBinaryUnits= true;
		}
		return result.toArray(new SearchResultGroup[result.size()]);
	}

	/**
	 * Search for all calls to the given <code>IMethodBinding</code> in the project
	 * that contains the compilation unit <code>fCUHandle</code>.
	 * @param methodBinding
	 * @param pm
	 * @param status
	 * @return an array of <code>SearchResultGroup</code>'s that identify the search matches
	 * @throws JavaModelException
	 */
	private SearchResultGroup[] searchForCallsTo(IMethodBinding methodBinding, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
		IMethod method= (IMethod) methodBinding.getJavaElement();
		SearchPattern searchPattern= createSearchPattern(method, methodBinding);
		if (searchPattern == null) {
			return new SearchResultGroup[0];
		}
		final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(searchPattern);
		engine.setFiltering(true, true);
		engine.setScope(createSearchScope(method, methodBinding));
		engine.setStatus(status);
		engine.searchPattern(new SubProgressMonitor(pm, 1));
		return (SearchResultGroup[]) engine.getResults();
	}

	/**
	 * Returns an array of <code>SearchResultGroup</code>'s containing all method
	 * calls in the Java project that invoke the constructor identified by the given
	 * <code>IMethodBinding</code>
	 * @param ctorBinding an <code>IMethodBinding</code> identifying a particular
	 * constructor signature to search for
	 * @param pm an <code>IProgressMonitor</code> to use during this potentially
	 * lengthy operation
	 * @param status
	 * @return an array of <code>SearchResultGroup</code>'s identifying all
	 * calls to the given constructor signature
	 * @throws JavaModelException
	 */
	private SearchResultGroup[] findAllCallsTo(IMethodBinding ctorBinding, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
		SearchResultGroup[] groups= excludeBinaryUnits(searchForCallsTo(ctorBinding, pm, status));

		return groups;
	}

	private IType findNonPrimaryType(String fullyQualifiedName, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
		SearchPattern p= SearchPattern.createPattern(fullyQualifiedName, IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
		if (p == null) {
			return null;
		}
		final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(p);

		engine.setFiltering(true, true);
		engine.setScope(RefactoringScopeFactory.create(fCtorBinding.getDeclaringClass().getJavaElement().getJavaProject()));
		engine.setStatus(status);
		engine.searchPattern(new SubProgressMonitor(pm, 1));

		SearchResultGroup[] groups= (SearchResultGroup[]) engine.getResults();

		for (SearchResultGroup rg : groups) {
			for (SearchMatch match : rg.getSearchResults()) {
				if (match.getAccuracy() == SearchMatch.A_ACCURATE) {
					return (IType) match.getElement();
				}
			}
		}
		return null;
	}

	/*
	 * @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkInput(org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
		try {
			pm.beginTask(RefactoringCoreMessages.IntroduceFactory_checking_preconditions, 1);
			RefactoringStatus result= new RefactoringStatus();

			if (fFactoryClassName != null)
				result.merge(setFactoryClass(fFactoryClassName));
			if (result.hasFatalError())
				return result;
			fArgTypes= fCtorBinding.getParameterTypes();
			fCtorIsVarArgs= fCtorBinding.isVarargs();
			fAllCallsTo= findAllCallsTo(fCtorBinding, pm, result);
			fFormalArgNames= findCtorArgNames();

			ICompilationUnit[]	affectedFiles= collectAffectedUnits(fAllCallsTo);
			result.merge(Checks.validateModifiesFiles(ResourceUtil.getFiles(affectedFiles), getValidationContext(), pm));

			if (fCallSitesInBinaryUnits)
				result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.IntroduceFactory_callSitesInBinaryClass));

			if(Modifier.isAbstract(fCtorBinding.getDeclaringClass().getModifiers())){
				result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.IntroduceFactory_abstractClass));
			}

			return result;
		} finally {
			pm.done();
		}
	}

	/**
	 * @return an array containing the argument names for the constructor
	 * identified by <code>fCtorBinding</code>, if available, or default
	 * names if unavailable (e.g. if the constructor resides in a binary unit).
	 */
	private String[] findCtorArgNames() {
		int			numArgs= fCtorBinding.getParameterTypes().length;
		String[]	names= new String[numArgs];

		CompilationUnit		ctorUnit= ASTNodes.getParent(fCtorOwningClass, CompilationUnit.class);
		MethodDeclaration	ctorDecl= (MethodDeclaration) ctorUnit.findDeclaringNode(fCtorBinding.getKey());

		if (ctorDecl != null) {
			List<SingleVariableDeclaration>	formalArgs= ctorDecl.parameters();
			int i= 0;
			if (formalArgs.size() == 0 && numArgs > 0 && fCtorBinding.isCompactConstructor()) {
				ASTNode parent= ctorDecl.getParent();
				if (parent instanceof RecordDeclaration) {
					RecordDeclaration recDecl= (RecordDeclaration) parent;
					formalArgs= recDecl.recordComponents();
				}
			}
			for(Iterator<SingleVariableDeclaration> iter= formalArgs.iterator(); iter.hasNext(); i++) {
				SingleVariableDeclaration svd= iter.next();

				names[i]= svd.getName().getIdentifier();
			}
			return names;
		}

		// Have no way of getting the formal argument names; just fake it.
		for(int i=0; i < numArgs; i++)
			names[i]= "arg" + (i+1); //$NON-NLS-1$

		return names;
	}

	/**
	 * Creates and returns a new MethodDeclaration that represents the factory method to be used in
	 * place of direct calls to the constructor in question.
	 *
	 * @param ast An AST used as a factory for various AST nodes
	 * @param ctorBinding binding for the constructor being wrapped
	 * @param unitRewriter the ASTRewrite to be used
	 * @return the new method declaration
	 * @throws CoreException if an exception occurs while accessing its corresponding resource
	 */
	private MethodDeclaration createFactoryMethod(AST ast, IMethodBinding ctorBinding, ASTRewrite unitRewriter) throws CoreException{
		MethodDeclaration		newMethod= ast.newMethodDeclaration();
		SimpleName				newMethodName= ast.newSimpleName(fNewMethodName);
		ClassInstanceCreation	newCtorCall= ast.newClassInstanceCreation();
		ReturnStatement			ret= ast.newReturnStatement();
		Block		body= ast.newBlock();
		List<Statement>		stmts= body.statements();
		String		retTypeName= ctorBinding.getName();

		createFactoryMethodSignature(ast, newMethod);

		newMethod.setName(newMethodName);
		newMethod.setBody(body);

		ITypeBinding declaringClass= fCtorBinding.getDeclaringClass();
		ITypeBinding[] ctorOwnerTypeParameters= declaringClass.getTypeParameters();

		setMethodReturnType(newMethod, retTypeName, ctorOwnerTypeParameters, ast);

		newMethod.modifiers().addAll(ASTNodeFactory.newModifiers(ast, Modifier.STATIC | Modifier.PUBLIC));

		setCtorTypeArguments(newCtorCall, retTypeName, ctorOwnerTypeParameters, ast);

		createFactoryMethodConstructorArgs(ast, newCtorCall);

		if (Modifier.isAbstract(declaringClass.getModifiers())) {
			AnonymousClassDeclaration decl= ast.newAnonymousClassDeclaration();
			CodeGenerationSettings settings= JavaPreferencesSettings.getCodeGenerationSettings(fCUHandle.getJavaProject());
			ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fFactoryCU, decl.getStartPosition(), fImportRewriter);
			for (IMethodBinding unImplementedMethod : getUnimplementedMethods(declaringClass)) {
				MethodDeclaration newMethodDecl= StubUtility2.createImplementationStub(fCUHandle, unitRewriter, fImportRewriter, context,
					unImplementedMethod, unImplementedMethod.getDeclaringClass(), settings, false, new NodeFinder(fFactoryCU, decl.getStartPosition(), 0).getCoveringNode());
				decl.bodyDeclarations().add(newMethodDecl);
			}
			newCtorCall.setAnonymousClassDeclaration(decl);
		}

		ret.setExpression(newCtorCall);
		stmts.add(ret);

		return newMethod;
	}

	private IMethodBinding[] getUnimplementedMethods(ITypeBinding binding) {
		IMethodBinding[] unimplementedMethods= StubUtility2Core.getUnimplementedMethods(binding, null);
		Arrays.sort(unimplementedMethods, new MethodsSourcePositionComparator(binding));
		return unimplementedMethods;
	}

	/**
	 * Sets the type being instantiated in the given constructor call, including
     * specifying any necessary type arguments.
	 * @param newCtorCall the constructor call to modify
	 * @param ctorTypeName the simple name of the type being instantiated
	 * @param ctorOwnerTypeParameters the formal type parameters of the type being
	 * instantiated
	 * @param ast utility object used to create AST nodes
	 */
	private void setCtorTypeArguments(ClassInstanceCreation newCtorCall, String ctorTypeName, ITypeBinding[] ctorOwnerTypeParameters, AST ast) {
        if (ctorOwnerTypeParameters.length == 0) // easy, just a simple type
            newCtorCall.setType(ASTNodeFactory.newType(ast, ctorTypeName));
        else {
            Type baseType= ast.newSimpleType(ast.newSimpleName(ctorTypeName));
            ParameterizedType newInstantiatedType= ast.newParameterizedType(baseType);
            List<Type> newInstTypeArgs= newInstantiatedType.typeArguments();

			for (ITypeBinding ctorOwnerTypeParameter : ctorOwnerTypeParameters) {
				Type typeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameter.getName());
				newInstTypeArgs.add(typeArg);
			}
            newCtorCall.setType(newInstantiatedType);
        }
	}

	/**
	 * Sets the return type of the factory method, including any necessary type
	 * arguments. E.g., for constructor <code>Foo()</code> in <code>Foo&lt;T&gt;</code>,
	 * the factory method defines a method type parameter <code>&lt;T&gt;</code> and
	 * returns a <code>Foo&lt;T&gt;</code>.
	 * @param newMethod the method whose return type is to be set
	 * @param retTypeName the simple name of the return type (without type parameters)
	 * @param ctorOwnerTypeParameters the formal type parameters of the type that the
	 * factory method instantiates (whose constructor is being encapsulated)
	 * @param ast utility object used to create AST nodes
	 */
	private void setMethodReturnType(MethodDeclaration newMethod, String retTypeName, ITypeBinding[] ctorOwnerTypeParameters, AST ast) {
        if (ctorOwnerTypeParameters.length == 0)
            newMethod.setReturnType2(ast.newSimpleType(ast.newSimpleName(retTypeName)));
        else {
            Type baseType= ast.newSimpleType(ast.newSimpleName(retTypeName));
            ParameterizedType newRetType= ast.newParameterizedType(baseType);
            List<Type> newRetTypeArgs= newRetType.typeArguments();

			for (ITypeBinding ctorOwnerTypeParameter : ctorOwnerTypeParameters) {
				Type retTypeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameter.getName());
				newRetTypeArgs.add(retTypeArg);
			}
            newMethod.setReturnType2(newRetType);
        }
	}

	/**
	 * Creates and adds the necessary argument declarations to the given factory method.<br>
	 * An argument is needed for each original constructor argument for which the
	 * evaluation of the actual arguments across all calls was not able to be
	 * pushed inside the factory method (e.g. arguments with side-effects, references
	 * to fields if the factory method is to be static or reside in a factory class,
	 * or arguments that varied across the set of constructor calls).<br>
	 * <code>fArgTypes</code> identifies such arguments by a <code>null</code> value.
	 * @param ast utility object used to create AST nodes
	 * @param newMethod the <code>MethodDeclaration</code> for the factory method
	 */
	private void createFactoryMethodSignature(AST ast, MethodDeclaration newMethod) {
		List<SingleVariableDeclaration> argDecls= newMethod.parameters();

		for(int i=0; i < fArgTypes.length; i++) {
			SingleVariableDeclaration argDecl= ast.newSingleVariableDeclaration();
			Type argType;

			if (i == (fArgTypes.length - 1) && fCtorIsVarArgs) {
				// The trailing varargs arg has an extra array dimension, compared to
				// what we need to pass to setType()...
				argType= typeNodeForTypeBinding(fArgTypes[i].getElementType(),
						fArgTypes[i].getDimensions()-1, ast);
				argDecl.setVarargs(true);
			} else
				argType= typeNodeForTypeBinding(fArgTypes[i], 0, ast);

			argDecl.setName(ast.newSimpleName(fFormalArgNames[i]));
			argDecl.setType(argType);
			argDecls.add(argDecl);
		}

		List<Type> exceptions= newMethod.thrownExceptionTypes();

		for (ITypeBinding ctorExcept : fCtorBinding.getExceptionTypes()) {
			exceptions.add(fImportRewriter.addImport(ctorExcept, ast));
		}

        copyTypeParameters(ast, newMethod);
	}

	/**
	 * Copies the constructor's parent type's type parameters, if any, as
	 * method type parameters of the new static factory method. (Recall
	 * that static methods can't refer to type arguments of the enclosing
	 * class, since they have no instance to serve as a context.)<br>
	 * Makes sure to copy the bounds from the owning type, to ensure that the
	 * return type of the factory method satisfies the bounds of the type
	 * being instantiated.<br>
	 * E.g., for ctor Foo() in the type Foo<T extends Number>, be sure that
	 * the factory method is declared as<br>
	 * <code>static <T extends Number> Foo<T> createFoo()</code><br>
	 * and not simply<br>
	 * <code>static <T> Foo<T> createFoo()</code><br>
	 * or the compiler will bark.
	 * @param ast utility object needed to create ASTNode's for the new method
	 * @param newMethod the method onto which to copy the type parameters
	 */
	private void copyTypeParameters(AST ast, MethodDeclaration newMethod) {
		List<TypeParameter> factoryMethodTypeParms= newMethod.typeParameters();
		for (ITypeBinding ctorOwnerTypeParm : fCtorBinding.getDeclaringClass().getTypeParameters()) {
			TypeParameter newParm= ast.newTypeParameter();
			List<Type> newParmBounds= newParm.typeBounds();
			newParm.setName(ast.newSimpleName(ctorOwnerTypeParm.getName()));
			for (ITypeBinding parmTypeBound : ctorOwnerTypeParm.getTypeBounds()) {
				if (parmTypeBound.isClass() && parmTypeBound.getSuperclass() == null) {
					continue;
				}
				Type newBound= fImportRewriter.addImport(parmTypeBound, ast);
				newParmBounds.add(newBound);
			}
			factoryMethodTypeParms.add(newParm);
		}
	}

	/**
	 * @param argType
	 * @param extraDims number of extra array dimensions to add to the resulting type
	 * @param ast
	 * @return a Type that describes the given ITypeBinding. If the binding
	 * refers to an object type, use the import rewriter to determine whether
	 * the reference requires a new import, or instead needs to be qualified.<br>
	 * Like ASTNodeFactory.newType(), but for the handling of imports.
	 */
	private Type typeNodeForTypeBinding(ITypeBinding argType, int extraDims, AST ast) {
		if (extraDims > 0) {
			return ast.newArrayType(typeNodeForTypeBinding(argType, 0, ast), extraDims);

		} else if (argType.isArray()) {
			Type elementType= typeNodeForTypeBinding(argType.getElementType(), extraDims, ast);
			return ast.newArrayType(elementType, argType.getDimensions());

		} else {
			return fImportRewriter.addImport(argType, ast);
		}
	}

	/**
	 * Create the list of actual arguments to the constructor call that is
	 * encapsulated inside the factory method, and associate the arguments
	 * with the given constructor call object.
	 * @param ast utility object used to create AST nodes
	 * @param newCtorCall the newly-generated constructor call to be wrapped inside
	 * the factory method
	 */
	private void createFactoryMethodConstructorArgs(AST ast, ClassInstanceCreation newCtorCall) {
		List<Expression> argList= newCtorCall.arguments();

		for(int i=0; i < fArgTypes.length; i++) {
			ASTNode ctorArg= ast.newSimpleName(fFormalArgNames[i]);

			argList.add((Expression) ctorArg);
		}
	}

	/**
	 * Updates the constructor call.
	 *
	 * @param ctorCall the ClassInstanceCreation to be marked as replaced
	 * @param unitRewriter the AST rewriter
	 * @param gd the edit group to use
	 */
	private void rewriteFactoryMethodCall(ClassInstanceCreation ctorCall, ASTRewrite unitRewriter, TextEditGroup gd) {
		AST ast= unitRewriter.getAST();
		MethodInvocation factoryMethodCall= ast.newMethodInvocation();

		ASTNode ctorCallParent= ctorCall.getParent();
		StructuralPropertyDescriptor ctorCallLocation= ctorCall.getLocationInParent();
		if (ctorCallLocation instanceof ChildListPropertyDescriptor) {
			ListRewrite ctorCallParentListRewrite= unitRewriter.getListRewrite(ctorCallParent, (ChildListPropertyDescriptor)ctorCallLocation);
			int index= ctorCallParentListRewrite.getOriginalList().indexOf(ctorCall);
			ctorCall= (ClassInstanceCreation)ctorCallParentListRewrite.getRewrittenList().get(index);
		} else {
			ctorCall= (ClassInstanceCreation)unitRewriter.get(ctorCallParent, ctorCallLocation);
		}

		ListRewrite actualFactoryArgs= unitRewriter.getListRewrite(factoryMethodCall, MethodInvocation.ARGUMENTS_PROPERTY);
		ListRewrite actualCtorArgs= unitRewriter.getListRewrite(ctorCall, ClassInstanceCreation.ARGUMENTS_PROPERTY);

		// Need to use a qualified name for the factory method if we're not
		// in the context of the class holding the factory.
		AbstractTypeDeclaration	callOwner= ASTNodes.getParent(ctorCall, AbstractTypeDeclaration.class);
		ITypeBinding callOwnerBinding= callOwner.resolveBinding();

		if (callOwnerBinding == null || !Bindings.equals(callOwner.resolveBinding(), fFactoryOwningClass.resolveBinding())) {
			String qualifier= fImportRewriter.addImport(fFactoryOwningClass.resolveBinding());
			factoryMethodCall.setExpression(ASTNodeFactory.newName(ast, qualifier));
		}

		factoryMethodCall.setName(ast.newSimpleName(fNewMethodName));

		List<Expression> actualCtorArgsList= actualCtorArgs.getRewrittenList();
		for (Expression actualCtorArg : actualCtorArgsList) {
			ASTNode movedArg;
			if (ASTNodes.isExistingNode(actualCtorArg)) {
				movedArg= unitRewriter.createMoveTarget(actualCtorArg);
			} else {
				unitRewriter.remove(actualCtorArg, gd);
				movedArg= actualCtorArg;
			}

			actualFactoryArgs.insertLast(movedArg, gd);
		}

		unitRewriter.replace(ctorCall, factoryMethodCall, gd);
	}

	/**
	 * @param unit
	 * @return true iff the given <code>ICompilationUnit</code> is the unit
	 * containing the original constructor
	 */
	private boolean isConstructorUnit(ICompilationUnit unit) {
		return unit.equals(ASTCreator.getCu(fCtorOwningClass));
	}

	/**
	 * @return true iff we should actually change the original constructor's
	 * visibility to <code>protected</code>. This takes into account the user-
	 * requested mode and whether the constructor's compilation unit is in
	 * source form.
	 */
	private boolean shouldProtectConstructor() {
		return fProtectConstructor && fCtorOwningClass != null;
	}

	/**
	 * Creates and adds the necessary change to make the constructor method protected.
	 * @param unitAST
	 * @param unitRewriter
	 * @param declGD
	 * @return false iff the constructor didn't exist (i.e. was implicit)
	 */
	private boolean protectConstructor(CompilationUnit unitAST, ASTRewrite unitRewriter, TextEditGroup declGD) {
		MethodDeclaration constructor= (MethodDeclaration) unitAST.findDeclaringNode(fCtorBinding.getKey());

		// No need to rewrite the modifiers if the visibility is what we already want it to be.
		if (constructor == null || (JdtFlags.getVisibilityCode(constructor)) == fConstructorVisibility)
			return false;
		ModifierRewrite.create(unitRewriter, constructor).setVisibility(fConstructorVisibility, declGD);
		return true;
	}

	/**
	 * Add all changes necessary on the <code>ICompilationUnit</code> in the given
	 * <code>SearchResultGroup</code> to implement the refactoring transformation
	 * to the given <code>CompilationUnitChange</code>.
	 * @param rg the <code>SearchResultGroup</code> for which changes should be created
	 * @param unitHandle
	 * @param unitChange the CompilationUnitChange object for the compilation unit in question
	 * @return <code>true</code> iff a change has been added
	 * @throws CoreException
	 */
	private boolean addAllChangesFor(SearchResultGroup rg, ICompilationUnit	unitHandle, CompilationUnitChange unitChange) throws CoreException {
//		ICompilationUnit	unitHandle= rg.getCompilationUnit();
		Assert.isTrue(rg == null || rg.getCompilationUnit() == unitHandle);
		CompilationUnit		unit= getASTFor(unitHandle);
		ASTRewrite			unitRewriter= ASTRewrite.create(unit.getAST());
		MultiTextEdit		root= new MultiTextEdit();
		boolean				someChange= false;

		unitChange.setEdit(root);
		fImportRewriter= StubUtility.createImportRewrite(unit, true);

		// First create the factory method
		if (unitHandle.equals(fFactoryUnitHandle)) {
			TextEditGroup	factoryGD= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_addFactoryMethod);

			createFactoryChange(unitRewriter, unit, factoryGD);
			unitChange.addTextEditGroup(factoryGD);
			someChange= true;
		}

		// Now rewrite all the constructor calls to use the factory method
		if (rg != null)
			if (replaceConstructorCalls(rg, unit, unitRewriter, unitChange))
				someChange= true;

		// Finally, make the constructor private, if requested.
		if (shouldProtectConstructor() && isConstructorUnit(unitHandle)) {
			TextEditGroup	declGD= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_protectConstructor);

			if (protectConstructor(unit, unitRewriter, declGD)) {
				unitChange.addTextEditGroup(declGD);
				someChange= true;
			}
		}

		if (someChange) {
			root.addChild(unitRewriter.rewriteAST());
			root.addChild(fImportRewriter.rewriteImports(null));
		}

		return someChange;
	}

	/**
	 * @param unitHandle
	 * @return an AST for the given compilation unit handle.<br>
	 * If this is the unit containing the selection or the unit in which the factory
	 * is to reside, checks the appropriate field (<code>fCU</code> or <code>fFactoryCU</code>,
	 * respectively) and initializes the field with a new AST only if not already done.
	 */
	private CompilationUnit getASTFor(ICompilationUnit unitHandle) {
		if (unitHandle.equals(fCUHandle)) { // is this the unit containing the selection?
			if (fCU == null) {
				fCU= ASTCreator.createAST(unitHandle, null);
				if (fCUHandle.equals(fFactoryUnitHandle)) // if selection unit and factory unit are the same...
					fFactoryCU= fCU; // ...make sure the factory unit gets initialized
			}
			return fCU;
		} else if (unitHandle.equals(fFactoryUnitHandle)) { // is this the "factory unit"?
			if (fFactoryCU == null)
				fFactoryCU= ASTCreator.createAST(unitHandle, null);
			return fFactoryCU;
		} else
			return ASTCreator.createAST(unitHandle, null);
	}

	/**
	 * Use the given <code>ASTRewrite</code> to replace direct calls to the constructor with calls
	 * to the newly-created factory method.
	 *
	 * @param rg the <code>SearchResultGroup</code> indicating all of the constructor references
	 * @param unit the <code>CompilationUnit</code> to be rewritten
	 * @param unitRewriter the rewriter
	 * @param unitChange the compilation unit change
	 * @throws CoreException
	 * @return true iff at least one constructor call site was rewritten.
	 */
	private boolean replaceConstructorCalls(SearchResultGroup rg, CompilationUnit unit, ASTRewrite unitRewriter, CompilationUnitChange unitChange) throws CoreException {
		Assert.isTrue(ASTCreator.getCu(unit).equals(rg.getCompilationUnit()));
		SearchMatch[] hits= rg.getSearchResults();
		/*
		 * Sort by descending offset, such that nested constructor calls are processed first. This
		 * is necessary, since they can only be moved into the factory method invocation after they
		 * have been rewritten.
		 */
		Arrays.sort(hits, (m1, m2) -> m2.getOffset() - m1.getOffset());

		boolean someCallPatched= false;

		for (SearchMatch hit : hits) {
			ASTNode ctrCall= getCtorCallAt(hit.getOffset(), hit.getLength(), unit);
			if (ctrCall instanceof ClassInstanceCreation) {
				TextEditGroup gd= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_replaceCalls);

				rewriteFactoryMethodCall((ClassInstanceCreation) ctrCall, unitRewriter, gd);
				unitChange.addTextEditGroup(gd);
				someCallPatched= true;
			} else if (ctrCall instanceof MethodRef) {
				TextEditGroup gd= new TextEditGroup(RefactoringCoreMessages.IntroduceFactoryRefactoring_replaceJavadocReference);

				rewriteJavadocReference((MethodRef) ctrCall, unitRewriter, gd);
				unitChange.addTextEditGroup(gd);
				someCallPatched= true;
			}
		}
		return someCallPatched;
	}

	private void rewriteJavadocReference(MethodRef javadocRef, ASTRewrite unitRewriter, TextEditGroup gd) {
		AST ast= unitRewriter.getAST();
		unitRewriter.replace(javadocRef.getName(), ast.newSimpleName(fNewMethodName), gd);
	}

	/**
	 * Look "in the vicinity" of the given range to find the <code>ClassInstanceCreation</code>
	 * node that this search hit identified. Necessary because the <code>SearchEngine</code>
	 * doesn't always cough up text extents that <code>NodeFinder.perform()</code> agrees with.
	 * @param start
	 * @param length
	 * @param unitAST
	 * @return return a {@link ClassInstanceCreation} or a {@link MethodRef} or <code>null</code> if this is really a constructor->constructor call (e.g. "this(...)")
	 * @throws CoreException
	 */
	private ASTNode getCtorCallAt(int start, int length, CompilationUnit unitAST) throws CoreException {
		ICompilationUnit unitHandle= ASTCreator.getCu(unitAST);
		ASTNode node= NodeFinder.perform(unitAST, start, length);

		if (node == null)
			throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
					Messages.format(RefactoringCoreMessages.IntroduceFactory_noASTNodeForConstructorSearchHit,
							new Object[] { Integer.toString(start), Integer.toString(start + length),
							    BasicElementLabels.getJavaCodeString(unitHandle.getSource().substring(start, start + length)),
								BasicElementLabels.getFileName(unitHandle) }),
					null));

		if (node instanceof ClassInstanceCreation) {
			if (((ClassInstanceCreation)node).getAnonymousClassDeclaration() != null) {
				// Cannot replace anonymous inner class, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=250660
				fConstructorVisibility= Modifier.PROTECTED;
				return null;
			}
			return node;
		} else if (node instanceof VariableDeclaration) {
			Expression	init= ((VariableDeclaration) node).getInitializer();

			if (init instanceof ClassInstanceCreation) {
				return init;
			} else if (init != null)
				throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
						Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedInitializerNodeType,
								new Object[] { BasicElementLabels.getJavaCodeString(init.toString()), BasicElementLabels.getFileName(unitHandle) }),
						null));
			else
				throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
						Messages.format(RefactoringCoreMessages.IntroduceFactory_noConstructorCallNodeInsideFoundVarbleDecl,
								BasicElementLabels.getJavaCodeString(node.toString())),
						null));
		} else if (node instanceof ConstructorInvocation) {
			// This is a call we can bypass; it's from one constructor flavor
			// to another flavor on the same class.
			return null;
		} else if (node instanceof SuperConstructorInvocation) {
			// This is a call we can bypass; it's from one constructor flavor
			// to another flavor on the same class.
			fConstructorVisibility= Modifier.PROTECTED;
			return null;
		} else if (node instanceof ExpressionStatement) {
			Expression	expr= ((ExpressionStatement) node).getExpression();

			if (expr instanceof ClassInstanceCreation)
				return expr;
			else
				throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
						Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit,
								new Object[] { BasicElementLabels.getJavaCodeString(expr.toString()), BasicElementLabels.getFileName(unitHandle) }),
						null));
		} else if (node instanceof SimpleName && (node.getParent() instanceof MethodDeclaration || node.getParent() instanceof AbstractTypeDeclaration)) {
			// We seem to have been given a hit for an implicit call to the base-class constructor.
			// Do nothing with this (implicit) call, but have to make sure we make the derived class
			// doesn't lose access to the base-class constructor (so make it 'protected', not 'private').
			fConstructorVisibility= Modifier.PROTECTED;
			return null;
		} else if (node instanceof MethodRef) {
			return node;
		} else
			throw new CoreException(JavaUIStatus.createError(IStatus.ERROR,
					Messages.format(RefactoringCoreMessages.IntroduceFactory_unexpectedASTNodeTypeForConstructorSearchHit,
							new Object[] { BasicElementLabels.getJavaElementName(node.getClass().getName() + "('" + node.toString() + "')"), BasicElementLabels.getFileName(unitHandle) }), //$NON-NLS-1$ //$NON-NLS-2$
					null));
	}

	/**
	 * Perform the AST rewriting necessary on the given <code>CompilationUnit</code> to create the
	 * factory method. The method will reside on the type identified by
	 * <code>fFactoryOwningClass</code>.
	 *
	 * @param unitRewriter the ASTRewrite to be used
	 * @param unit the <code>CompilationUnit</code> where factory method will be created
	 * @param gd the <code>GroupDescription</code> to associate with the changes made
	 * @throws CoreException if an exception occurs while accessing its corresponding resource
	 */
	private void createFactoryChange(ASTRewrite unitRewriter, CompilationUnit unit, TextEditGroup gd) throws CoreException {
		// ================================================================================
		// First add the factory itself (method, class, and interface as needed/directed by user)
		AST				ast= unit.getAST();

		fFactoryMethod= createFactoryMethod(ast, fCtorBinding, unitRewriter);

		AbstractTypeDeclaration	factoryOwner= (AbstractTypeDeclaration) unit.findDeclaringNode(fFactoryOwningClass.resolveBinding().getKey());
		fImportRewriter.addImport(fCtorOwningClass.resolveBinding());

		int	idx= BodyDeclarationRewrite.getInsertionIndex(fFactoryMethod, factoryOwner.bodyDeclarations());

		if (idx < 0) idx= 0; // Guard against bug in getInsertionIndex()
		unitRewriter.getListRewrite(factoryOwner, factoryOwner.getBodyDeclarationsProperty()).insertAt(fFactoryMethod, idx, gd);
	}

	@Override
	public Change createChange(IProgressMonitor pm) throws CoreException {
		try {
			pm.beginTask(RefactoringCoreMessages.IntroduceFactory_createChanges, fAllCallsTo.length);
			final ITypeBinding binding= fFactoryOwningClass.resolveBinding();
			final Map<String, String> arguments= new HashMap<>();
			String project= null;
			IJavaProject javaProject= fCUHandle.getJavaProject();
			if (javaProject != null)
				project= javaProject.getElementName();
			int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE;
			if (binding.isNested() && !binding.isMember())
				flags|= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
			final String description= Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(fCtorOwningClass.getName().getIdentifier()));
			final String header= Messages.format(RefactoringCoreMessages.IntroduceFactory_descriptor_description, new String[] { BasicElementLabels.getJavaElementName(fNewMethodName), BindingLabelProvider.getBindingLabel(binding, JavaElementLabels.ALL_FULLY_QUALIFIED), BindingLabelProvider.getBindingLabel(fCtorBinding, JavaElementLabels.ALL_FULLY_QUALIFIED)});
			final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
			comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_original_pattern, BindingLabelProvider.getBindingLabel(fCtorBinding, JavaElementLabels.ALL_FULLY_QUALIFIED)));
			comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_factory_pattern, BasicElementLabels.getJavaElementName(fNewMethodName)));
			comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceFactoryRefactoring_owner_pattern, BindingLabelProvider.getBindingLabel(binding, JavaElementLabels.ALL_FULLY_QUALIFIED)));
			if (fProtectConstructor)
				comment.addSetting(RefactoringCoreMessages.IntroduceFactoryRefactoring_declare_private);
			final IntroduceFactoryDescriptor descriptor= RefactoringSignatureDescriptorFactory.createIntroduceFactoryDescriptor(project, description, comment.asString(), arguments, flags);
			arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project, fCUHandle));
			arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fNewMethodName);
			arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1, JavaRefactoringDescriptorUtil.elementToHandle(project, binding.getJavaElement()));
			arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION, Integer.toString(fSelectionStart) + " " + Integer.toString(fSelectionLength)); //$NON-NLS-1$
			arguments.put(ATTRIBUTE_PROTECT, Boolean.toString(fProtectConstructor));
			final DynamicValidationStateChange result= new DynamicValidationRefactoringChange(descriptor, RefactoringCoreMessages.IntroduceFactory_name);
			boolean hitInFactoryClass= false;
			boolean hitInCtorClass= false;
			for (SearchResultGroup rg : fAllCallsTo) {
				ICompilationUnit unitHandle= rg.getCompilationUnit();
				CompilationUnitChange cuChange= new CompilationUnitChange(getName(), unitHandle);

				if (addAllChangesFor(rg, unitHandle, cuChange))
					result.add(cuChange);

				if (unitHandle.equals(fFactoryUnitHandle))
					hitInFactoryClass= true;
				if (unitHandle.equals(ASTCreator.getCu(fCtorOwningClass)))
					hitInCtorClass= true;

				pm.worked(1);
				if (pm.isCanceled())
					throw new OperationCanceledException();
			}
			if (!hitInFactoryClass) { // Handle factory class if no search hits there
				CompilationUnitChange cuChange= new CompilationUnitChange(getName(), fFactoryUnitHandle);
				addAllChangesFor(null, fFactoryUnitHandle, cuChange);
				result.add(cuChange);
			}
			if (!hitInCtorClass && !fFactoryUnitHandle.equals(ASTCreator.getCu(fCtorOwningClass))) { // Handle constructor-owning class if no search hits there
				CompilationUnitChange cuChange= new CompilationUnitChange(getName(), ASTCreator.getCu(fCtorOwningClass));
				addAllChangesFor(null, ASTCreator.getCu(fCtorOwningClass), cuChange);
				result.add(cuChange);
			}
			return result;
		} finally {
			pm.done();
		}
	}

	@Override
	public String getName() {
		return RefactoringCoreMessages.IntroduceFactory_name;
	}

	/**
	 * Returns the name to be used for the generated factory method.
	 * @return the new method name
	 */
	public String getNewMethodName() {
		return fNewMethodName;
	}

	/**
	 * Sets the name to be used for the generated factory method.<br>
	 * Returns a <code>RefactoringStatus</code> that indicates whether the
	 * given name is valid for the new factory method.
	 * @param newMethodName the name to be used for the generated factory method
	 * @return the resulting status
	 */
	public RefactoringStatus setNewMethodName(String newMethodName) {
		Assert.isNotNull(newMethodName);
		fNewMethodName = newMethodName;

		RefactoringStatus stat= Checks.checkMethodName(newMethodName, fCUHandle);

		stat.merge(isUniqueMethodName(newMethodName));

		return stat;
	}

	/**
	 * @param methodName
	 * @return a <code>RefactoringStatus</code> that identifies whether the
	 * the name <code>newMethodName</code> is available to use as the name of
	 * the new factory method within the factory-owner class (either a to-be-
	 * created factory class or the constructor-owning class, depending on the
	 * user options).
	 */
	private RefactoringStatus isUniqueMethodName(String methodName) {
		ITypeBinding declaringClass= fCtorBinding.getDeclaringClass();
		if (Bindings.findMethodInType(declaringClass, methodName, fCtorBinding.getParameterTypes()) != null) {
			String format= Messages.format(RefactoringCoreMessages.IntroduceFactory_duplicateMethodName, BasicElementLabels.getJavaElementName(methodName));
			return RefactoringStatus.createErrorStatus(format);
		}
 		return new RefactoringStatus();
	}

	/**
	 * Returns true iff the selected constructor can be protected.
	 * @return return <code>true</code> if the constructor can be made protected
	 */
	public boolean canProtectConstructor() {
		return !fCtorBinding.isSynthetic()
				&& fFactoryCU.findDeclaringNode(fCtorBinding.getKey()) != null
				&& !fCtorBinding.isCanonicalConstructor();
	}

	/**
	 * If the argument is true, change the visibility of the constructor to
	 * <code>protected</code>, thereby encapsulating it.
	 * @param protectConstructor
	 */
	public void setProtectConstructor(boolean protectConstructor) {
		fProtectConstructor = protectConstructor;
	}

	/**
	 * Returns the project on behalf of which this refactoring was invoked.
	 * @return returns the Java project
	 */
	public IJavaProject getProject() {
		return fCUHandle.getJavaProject();
	}

	/**
	 * Sets the class on which the generated factory method is to be placed.
	 * @param fullyQualifiedTypeName an <code>IType</code> referring to an existing class
	 * @return return the resulting status
	 */
	public RefactoringStatus setFactoryClass(String fullyQualifiedTypeName) {
		IType factoryType;

		try {
			factoryType= findFactoryClass(fullyQualifiedTypeName);
			if (factoryType == null)
				return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.IntroduceFactory_noSuchClass, BasicElementLabels.getJavaElementName(fullyQualifiedTypeName)));

			if (factoryType.isAnnotation())
				return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryMethodOnAnnotation);
			if (factoryType.isInterface())
				return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryMethodOnInterface);
		} catch (JavaModelException e) {
			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantCheckForInterface);
		}

		ICompilationUnit	factoryUnitHandle= factoryType.getCompilationUnit();

		if (factoryType.isBinary())
			return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceFactory_cantPutFactoryInBinaryClass);
		else {
			try {
				if (!fFactoryUnitHandle.equals(factoryUnitHandle)) {
					fFactoryCU= getASTFor(factoryUnitHandle);
					fFactoryUnitHandle= factoryUnitHandle;
				}
				fFactoryOwningClass= ASTNodes.getParent(NodeFinder.perform(fFactoryCU, factoryType.getNameRange()), AbstractTypeDeclaration.class);

				String factoryPkg= factoryType.getPackageFragment().getElementName();
				String ctorPkg= fCtorOwningClass.resolveBinding().getPackage().getName();

				if (!factoryPkg.equals(ctorPkg))
					fConstructorVisibility= Modifier.PUBLIC;
				else if (fFactoryOwningClass != fCtorOwningClass)
					fConstructorVisibility= 0; // No such thing as Modifier.PACKAGE...


				if (fFactoryOwningClass != fCtorOwningClass)
					fConstructorVisibility= 0; // No such thing as Modifier.PACKAGE...

			} catch (JavaModelException e) {
				return RefactoringStatus.createFatalErrorStatus(e.getMessage());
			}
			return new RefactoringStatus();
		}
	}

	/**
	 * Finds the factory class associated with the fully qualified name.
	 * @param fullyQualifiedTypeName the fully qualified type name
	 * @return the factory class, or <code>null</code> if not found
	 * @throws JavaModelException if an error occurs while finding the factory class
	 */
	private IType findFactoryClass(String fullyQualifiedTypeName) throws JavaModelException {
		IType factoryType= getProject().findType(fullyQualifiedTypeName);
		if (factoryType == null) // presumably a non-primary type; try the search engine
			factoryType= findNonPrimaryType(fullyQualifiedTypeName, new NullProgressMonitor(), new RefactoringStatus());
		return factoryType;
	}

	/**
	 * Returns the name of the class on which the generated factory method is
	 * to be placed.
	 * @return return the factory class name
	 */
	public String getFactoryClassName() {
		return fFactoryOwningClass.resolveBinding().getQualifiedName();
	}

	private RefactoringStatus initialize(JavaRefactoringArguments arguments) {
		final String selection= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION);
		if (selection != null) {
			int offset= -1;
			int length= -1;
			final StringTokenizer tokenizer= new StringTokenizer(selection);
			if (tokenizer.hasMoreTokens())
				offset= Integer.parseInt(tokenizer.nextToken());
			if (tokenizer.hasMoreTokens())
				length= Integer.parseInt(tokenizer.nextToken());
			if (offset >= 0 && length >= 0) {
				fSelectionStart= offset;
				fSelectionLength= length;
			} else
				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION}));
		} else
			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION));
		String handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT);
		if (handle != null) {
			final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false);
			if (element == null || !element.exists() || element.getElementType() != IJavaElement.COMPILATION_UNIT)
				return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.INTRODUCE_FACTORY);
			else {
				fCUHandle= (ICompilationUnit) element;
	        	initialize();
			}
		} else
			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
		handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1);
		if (handle != null) {
			final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false);
			if (element == null || !element.exists() || element.getElementType() != IJavaElement.TYPE)
				return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.INTRODUCE_FACTORY);
			else {
				final IType type= (IType) element;
				fFactoryClassName= type.getFullyQualifiedName();
			}
		} else
			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
		final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME);
		if (name != null && !"".equals(name)) //$NON-NLS-1$
			fNewMethodName= name;
		else
			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME));
		final String protect= arguments.getAttribute(ATTRIBUTE_PROTECT);
		if (protect != null) {
			fProtectConstructor= Boolean.parseBoolean(protect);
		} else
			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_PROTECT));
		return new RefactoringStatus();
	}
}
