Bug 568987 - [15] record compact constructor - Refactor -> Introduce Factory causes NPE

Change-Id: Ibfff5e094d882b8bc37756bfef9c0b579dd73db6
Signed-off-by: Kalyan Prasad Tatavarthi <kalyan_prasad@in.ibm.com>
diff --git a/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/566943/RecCompConst.java b/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/566943/RecCompConst.java
new file mode 100644
index 0000000..7c368ac
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/566943/RecCompConst.java
@@ -0,0 +1,10 @@
+package p;
+
+import java.util.List;
+
+public record RecCompConst(int a, char c, String b) {
+
+	public /*[*/RecCompConst/*]*/ {
+    }
+
+}
diff --git a/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/566943/RecCompConst_out.java b/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/566943/RecCompConst_out.java
new file mode 100644
index 0000000..e889e93
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/566943/RecCompConst_out.java
@@ -0,0 +1,14 @@
+package p;
+
+import java.util.List;
+
+public record RecCompConst(int a, char c, String b) {
+
+	public static RecCompConst createRecCompConst(int a, char c, String b) {
+		return new RecCompConst(a, c, b);
+	}
+
+	public /*[*/RecCompConst/*]*/ {
+    }
+
+}
diff --git a/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/568987/RecCanConst.java b/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/568987/RecCanConst.java
new file mode 100644
index 0000000..2c388d4
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/568987/RecCanConst.java
@@ -0,0 +1,10 @@
+package p;
+
+import java.util.List;
+
+public record RecCanConst(int a, char c, String b) {
+
+	public /*[*/RecCanConst/*]*/(int a, char c, String b) {
+    }
+
+}
diff --git a/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/568987/RecCanConst_out.java b/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/568987/RecCanConst_out.java
new file mode 100644
index 0000000..1615a81
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests.refactoring/resources/IntroduceFactory/Bugzilla/568987/RecCanConst_out.java
@@ -0,0 +1,14 @@
+package p;
+
+import java.util.List;
+
+public record RecCanConst(int a, char c, String b) {
+
+	public static RecCanConst createRecCanConst(int a, char c, String b) {
+		return new RecCanConst(a, c, b);
+	}
+
+	public /*[*/RecCanConst/*]*/(int a, char c, String b) {
+    }
+
+}
diff --git a/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/AllRefactoringTests.java b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/AllRefactoringTests.java
index 0a227c8..5147eed 100644
--- a/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/AllRefactoringTests.java
+++ b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/AllRefactoringTests.java
@@ -47,6 +47,7 @@
 	IntroduceParameterTests.class,
 	IntroduceParameterTests1d7.class,
 	IntroduceFactoryTests.class,
+	IntroduceFactoryTests15.class,
 
 	//-- structure
 	ChangeSignatureTests.class,
diff --git a/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTests.java b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTests.java
index d7208db..c0e51f3 100644
--- a/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTests.java
+++ b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTests.java
@@ -14,42 +14,16 @@
  *******************************************************************************/
 package org.eclipse.jdt.ui.tests.refactoring;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
 import org.junit.Test;
 
-import org.eclipse.jdt.testplugin.JavaProjectHelper;
-
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.NullProgressMonitor;
-
 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
 
-import org.eclipse.jdt.core.ICompilationUnit;
-import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.core.IPackageFragment;
-import org.eclipse.jdt.core.IPackageFragmentRoot;
-import org.eclipse.jdt.core.ISourceRange;
-import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jdt.core.SourceRange;
-
-import org.eclipse.jdt.internal.corext.refactoring.code.IntroduceFactoryRefactoring;
-
 import org.eclipse.jdt.ui.tests.refactoring.rules.Java1d6Setup;
 
 /**
  * @author rfuhrer@watson.ibm.com
  */
-public class IntroduceFactoryTests extends GenericRefactoringTest {
+public class IntroduceFactoryTests extends IntroduceFactoryTestsBase{
 	private static final String REFACTORING_PATH= "IntroduceFactory/";
 
 	public IntroduceFactoryTests() {
@@ -61,467 +35,6 @@
 		return REFACTORING_PATH;
 	}
 
-	/**
-	 * Produces a test file name based on the name of this JUnit testcase.
-	 * For input files, trims off the trailing part of the test name that
-	 * begins with a '_', to get rid of the options part, so that we can
-	 * have a single (suite of) input file(s) but several outputs dependent
-	 * on the option settings.
-	 * @param input true iff the requested file is an input file.
-	 * @return the name of the test file, with a trailing "_in.java" if an input
-	 * file and a trailing "_XXX.java" if an output file and the test name/options
-	 * are "_XXX".
-	 */
-	private String getSimpleTestFileName(boolean input) {
-		String	testName = getName();
-		int		usIdx=  testName.indexOf('_');
-		int		endIdx= (usIdx >= 0) ? usIdx : testName.length();
-		String	fileName = (input ? (testName.substring(4, endIdx) + "_in") : testName.substring(4));
-
-		return fileName + ".java";
-	}
-
-	/**
-	 * Produces a test file name based on the name of this JUnit testcase,
-	 * like getSimpleTestFileName(), but also prepends the appropriate version
-	 * of the resource path (depending on the value of <code>positive</code>).
-	 * Test files are assumed to be located in the resources directory.
-	 * @param positive true iff the requested file is for a positive unit test
-	 * @param input true iff the requested file is an input file
-	 * @return the test file name
-	 */
-	private String getTestFileName(boolean positive, boolean input) {
-		String path= TEST_PATH_PREFIX + getRefactoringPath();
-
-		path += (positive ? "positive/": "negative/");
-		return path + getSimpleTestFileName(input);
-	}
-
-	/**
-	 * Produces a compilation unit from an input source file whose name
-	 * is based on the testcase name.
-	 * Test files are assumed to be located in the resources directory.
-	 * @param pack
-	 * @param positive
-	 * @param input
-	 * @return the ICompilationUnit created from the specified test file
-	 * @throws Exception
-	 */
-	private ICompilationUnit createCUForSimpleTest(IPackageFragment pack,
-												  boolean positive, boolean input)
-		throws Exception
-	{
-		String	fileName= getTestFileName(positive, input);
-		String	cuName= getSimpleTestFileName(input);
-
-		return createCU(pack, cuName, getFileContents(fileName));
-	}
-
-	/**
-	 * Produces a test file name based on the name of this JUnit testcase,
-	 * like getSimpleTestFileName(), but also prepends the appropriate version
-	 * of the resource path (depending on the value of <code>positive</code>).
-	 * Test files are assumed to be located in the resources directory.
-	 * @param project the project
-	 * @param pack the package fragment
-	 * @param fileName the file name
-	 * @param input true iff the requested file is an input file
-	 * @return the test file name
-	 */
-	private String getBugTestFileName(IJavaProject project, IPackageFragment pack, String fileName, boolean input) {
-		String testName= getName();
-		String testNumber= testName.substring("test".length());//$NON-NLS-1$
-		String path= TEST_PATH_PREFIX + getRefactoringPath() + "Bugzilla/" + testNumber + "/" +
-									(project == null ? "" : project.getElementName() + "/") +
-									(pack == getPackageP() ? "" : pack.getElementName() + "/");
-
-		return path + fileName + (input ? "" : "_out") + ".java";//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-	}
-
-	/**
-	 * Produces a compilation unit from an input source file whose path
-	 * is based on the testcase name, but whose basename is supplied by
-	 * the caller.
-	 * Test files are assumed to be located in the resources directory.
-	 * @param project can be null if only 1 project exists in the test workspace
-	 * @param pack
-	 * @param baseName
-	 * @param input
-	 * @return the ICompilationUnit created from the specified test file
-	 * @throws Exception
-	 */
-	private ICompilationUnit createCUForBugTestCase(IJavaProject project,
-													IPackageFragment pack, String baseName, boolean input)
-		throws Exception
-	{
-		String	fileName= getBugTestFileName(project, pack, baseName, input);
-		String	cuName= baseName + (input ? "" : "_out") + ".java";
-
-		return createCU(pack, cuName, getFileContents(fileName));
-	}
-
-	static final String SELECTION_START_HERALD= "/*[*/";
-	static final String SELECTION_END_HERALD= "/*]*/";
-
-	/**
-	 * Finds and returns the selection markers in the given source string,
-	 * i.e. the first occurrences of <code>SELECTION_START_HERALD</code> and
-	 * <code>SELECTION_END_HERALD</code>. Fails an assertion if either of these
-	 * markers is not present in the source string.
-	 * @param source
-	 * @return an ISourceRange representing the marked selection
-	 * @throws Exception
-	 */
-	private ISourceRange findSelectionInSource(String source) throws Exception {
-		int		begin= source.indexOf(SELECTION_START_HERALD) + SELECTION_START_HERALD.length();
-		int		end= source.indexOf(SELECTION_END_HERALD);
-
-		if (begin < SELECTION_START_HERALD.length())
-			fail("No selection start comment in input source file!");
-		if (end < 0)
-			fail("No selection end comment in input source file!");
-
-		return new SourceRange(begin, end-begin);
-	}
-
-	private void doSingleUnitTest(boolean protectConstructor, ICompilationUnit cu, String outputFileName) throws Exception, JavaModelException, IOException {
-		ISourceRange		selection= findSelectionInSource(cu.getSource());
-		IntroduceFactoryRefactoring	ref= new IntroduceFactoryRefactoring(cu, selection.getOffset(), selection.getLength());
-
-		ref.setProtectConstructor(protectConstructor);
-
-		RefactoringStatus	activationResult= ref.checkInitialConditions(new NullProgressMonitor());
-
-		assertTrue("activation was supposed to be successful", activationResult.isOK());
-
-		RefactoringStatus	checkInputResult= ref.checkFinalConditions(new NullProgressMonitor());
-
-		if (!checkInputResult.isOK()) {
-			performChange(ref, false);
-
-			String newSource = cu.getSource();
-
-			System.err.println("!!!Precondition failed for " + getName() + "!!!");
-			System.err.println("Compile-time error: " + checkInputResult.toString());
-			System.err.println("Offending source:");
-			System.err.print(newSource);
-			fail("precondition was supposed to pass but was " + checkInputResult.toString());
-		}
-
-		performChange(ref, false);
-
-		String newSource = cu.getSource();
-
-		assertEqualLines(getName() + ": ", getFileContents(outputFileName), newSource);
-	}
-
-	private void doSingleUnitTestWithWarning(boolean protectConstructor, ICompilationUnit cu, String outputFileName) throws Exception, JavaModelException, IOException {
-		ISourceRange selection= findSelectionInSource(cu.getSource());
-		IntroduceFactoryRefactoring ref= new IntroduceFactoryRefactoring(cu, selection.getOffset(), selection.getLength());
-
-		ref.setProtectConstructor(protectConstructor);
-
-		RefactoringStatus activationResult= ref.checkInitialConditions(new NullProgressMonitor());
-
-		assertTrue("activation was supposed to be successful", activationResult.isOK());
-
-		RefactoringStatus checkInputResult= ref.checkFinalConditions(new NullProgressMonitor());
-
-		assertEquals(RefactoringStatus.WARNING, checkInputResult.getSeverity());
-		performChange(ref, false);
-
-		String newSource= cu.getSource();
-
-		assertEqualLines(getName() + ": ", getFileContents(outputFileName), newSource);
-	}
-
-	/**
-	 * Tests the IntroduceFactoryRefactoring refactoring on a single input source file
-	 * whose name is the test name (minus the "test" prefix and any trailing
-	 * options indicator such as "_FFF"), and compares the transformed code
-	 * to a source file whose name is the test name (minus the "test" prefix).
-	 * Test files are assumed to be located in the resources directory.
-	 * @param protectConstructor true iff IntroduceFactoryRefactoring should make the constructor private
-	 * @throws Exception
-	 */
-	void singleUnitHelper(boolean protectConstructor)
-		throws Exception
-	{
-		ICompilationUnit	cu= createCUForSimpleTest(getPackageP(), true, true);
-
-		doSingleUnitTest(protectConstructor, cu, getTestFileName(true, false));
-	}
-
-	/**
-	 * Tests the IntroduceFactoryRefactoring refactoring on a single input source file
-	 * whose name is the test name (minus the "test" prefix and any trailing
-	 * options indicator such as "_FFF"), and compares the transformed code
-	 * to a source file whose name is the test name (minus the "test" prefix).
-	 * Test files are assumed to be located in the resources directory.
-	 * @param baseFileName the base file name
-	 * @param protectConstructor true iff IntroduceFactoryRefactoring should make the constructor private
-	 * @throws Exception
-	 */
-	protected void singleUnitBugHelper(String baseFileName, boolean protectConstructor)
-		throws Exception
-	{
-		ICompilationUnit	cu= createCUForBugTestCase(null, getPackageP(), baseFileName, true);
-
-		doSingleUnitTest(protectConstructor, cu, getBugTestFileName(null, getPackageP(), baseFileName, false));
-	}
-
-	protected void singleUnitBugHelperWithWarning(String baseFileName, boolean protectConstructor)
-			throws Exception
-	{
-		ICompilationUnit cu= createCUForBugTestCase(null, getPackageP(), baseFileName, true);
-
-		doSingleUnitTestWithWarning(protectConstructor, cu, getBugTestFileName(null, getPackageP(), baseFileName, false));
-	}
-
-	/**
-	 * Like singleUnitHelper(), but allows for the specification of the names of
-	 * the generated factory method, class, and interface, as appropriate.
-	 * @param factoryMethodName the name to use for the generated factory method
-	 * @param factoryClassName the name of the factory class
-	 * @throws Exception
-	 */
-	void namesHelper(String factoryMethodName, String factoryClassName)
-		throws Exception
-	{
-		ICompilationUnit	cu= createCUForSimpleTest(getPackageP(), true, true);
-		ISourceRange		selection= findSelectionInSource(cu.getSource());
-		IntroduceFactoryRefactoring	ref= new IntroduceFactoryRefactoring(cu, selection.getOffset(), selection.getLength());
-
-		RefactoringStatus	activationResult= ref.checkInitialConditions(new NullProgressMonitor());
-
-		assertTrue("activation was supposed to be successful", activationResult.isOK());
-
-		if (factoryMethodName != null)
-			ref.setNewMethodName(factoryMethodName);
-		if (factoryClassName != null)
-			ref.setFactoryClass(factoryClassName);
-
-		RefactoringStatus	checkInputResult= ref.checkFinalConditions(new NullProgressMonitor());
-
-		assertTrue("precondition was supposed to pass but was " + checkInputResult.toString(), checkInputResult.isOK());
-
-		performChange(ref, false);
-
-		String newSource = cu.getSource();
-
-		assertEqualLines(getName() + ": ", getFileContents(getTestFileName(true, false)), newSource);
-	}
-
-	/**
-	 * Creates a compilation unit for a source file with a given base name (plus
-	 * "_in" suffix) in the given package. The source file is assumed to be
-	 * located in the test resources directory.<br>
-	 * Currently only handles positive tests.
-	 * @param fileName the base name of the source file (minus the "_in" suffix)
-	 * @param pack an IPackageFragment for the containing package
-	 * @return the ICompilationUnit for the newly-created unit
-	 * @throws Exception
-	 */
-	private ICompilationUnit createCUFromFileName(String fileName, IPackageFragment pack) throws Exception {
-		String fullName = TEST_PATH_PREFIX + getRefactoringPath() + "positive/" + fileName + "_in.java";
-
-		return createCU(pack, fileName + "_in.java", getFileContents(fullName));
-	}
-
-	private void doMultiUnitTest(ICompilationUnit[] CUs, String testPath, String[] outputFileBaseNames, String factoryClassName) throws Exception, JavaModelException, IOException {
-		ISourceRange selection= findSelectionInSource(CUs[0].getSource());
-		IntroduceFactoryRefactoring	ref= new IntroduceFactoryRefactoring(CUs[0], selection.getOffset(), selection.getLength());
-
-		RefactoringStatus activationResult= ref.checkInitialConditions(new NullProgressMonitor());
-
-		assertTrue("activation was supposed to be successful", activationResult.isOK());
-
-		if (factoryClassName != null)
-			ref.setFactoryClass(factoryClassName);
-
-		RefactoringStatus	checkInputResult= ref.checkFinalConditions(new NullProgressMonitor());
-
-		assertTrue("precondition was supposed to pass but was " + checkInputResult.toString(), checkInputResult.isOK());
-
-		performChange(ref, false);
-
-		String	testName= getName();
-
-		for (int i = 0; i < CUs.length; i++) {
-			int optIdx= testName.indexOf("_");
-			String testOptions= (optIdx >= 0) ? testName.substring(optIdx) : "";
-			String outFileName= testPath + outputFileBaseNames[i] + testOptions + "_out.java";
-			String xformedSrc= CUs[i].getSource();
-			String expectedSrc= getFileContents(outFileName);
-
-			assertEqualLines(getName() + ": ", expectedSrc, xformedSrc);
-		}
-	}
-
-	/**
-	 * Tests the IntroduceFactoryRefactoring refactoring on a set of input source files
-	 * whose names are supplied in the <code>fileBaseNames</code> argument,
-	 * and compares the transformed code to source files whose names are
-	 * the input base names plus the options suffix (e.g. "_FFF").
-	 * Test files are assumed to be located in the resources directory.
-	 * @param staticFactoryMethod true iff IntroduceFactoryRefactoring should make the factory method static
-	 * @param inputFileBaseNames an array of input source file base names
-	 * @throws Exception
-	 */
-	void multiUnitHelper(boolean staticFactoryMethod, String[] inputFileBaseNames)
-		throws Exception
-	{
-		IPackageFragment	pkg= getPackageP();
-		ICompilationUnit	CUs[]= new ICompilationUnit[inputFileBaseNames.length];
-
-		for (int i = 0; i < inputFileBaseNames.length; i++)
-			CUs[i] = createCUFromFileName(inputFileBaseNames[i], pkg);
-
-		String	testPath= TEST_PATH_PREFIX + getRefactoringPath() + "positive/";
-
-		doMultiUnitTest(CUs, testPath, inputFileBaseNames, null);
-	}
-
-	/**
-	 * Tests the IntroduceFactoryRefactoring refactoring on a set of input source files
-	 * whose names are supplied in the <code>fileBaseNames</code> argument,
-	 * and compares the transformed code to source files whose names are
-	 * the input base names plus the options suffix (e.g. "_FFF").
-	 * Test files are assumed to be located in the resources directory.
-	 * @param staticFactoryMethod true iff IntroduceFactoryRefactoring should make the factory method static
-	 * @param inputFileBaseNames an array of input source file base names
-	 * @param factoryClassName the fully-qualified name of the class to receive the factory method, or null
-	 * if the factory method is to be placed on the class defining the given constructor
-	 * @throws Exception
-	 */
-	void multiUnitBugHelper(boolean staticFactoryMethod, String[] inputFileBaseNames, String factoryClassName)
-		throws Exception
-	{
-		ICompilationUnit CUs[]= new ICompilationUnit[inputFileBaseNames.length];
-
-		for(int i= 0; i < inputFileBaseNames.length; i++) {
-			int pkgEnd= inputFileBaseNames[i].lastIndexOf('/')+1;
-			boolean explicitPkg= (pkgEnd > 0);
-			IPackageFragment pkg= explicitPkg ? getRoot().createPackageFragment(inputFileBaseNames[i].substring(0, pkgEnd-1), true, new NullProgressMonitor()) : getPackageP();
-
-			CUs[i]= createCUForBugTestCase(null, pkg, inputFileBaseNames[i].substring(pkgEnd), true);
-		}
-
-		String	testName= getName();
-		String	testNumber= testName.substring("test".length());
-		String	testPath= TEST_PATH_PREFIX + getRefactoringPath() + "Bugzilla/" + testNumber + "/";
-
-		doMultiUnitTest(CUs, testPath, inputFileBaseNames, factoryClassName);
-	}
-
-	void multiProjectBugHelper(String[] inputFileBaseNames, String[] dependencies) throws Exception {
-		Map<String, Set<String>> projName2PkgNames= collectProjectPackages(inputFileBaseNames);
-		Map<String, IJavaProject> projName2Project= new HashMap<>();
-		Map<IJavaProject, IPackageFragmentRoot> proj2PkgRoot= new HashMap<>();
-
-		try {
-			createProjectPackageStructure(projName2PkgNames, projName2Project, proj2PkgRoot);
-
-			ICompilationUnit[] CUs= createCUs(inputFileBaseNames, projName2Project, proj2PkgRoot);
-
-			addProjectDependencies(dependencies, projName2Project);
-
-			String testName= getName();
-			String testNumber= testName.substring("test".length());
-			String testPath= TEST_PATH_PREFIX + getRefactoringPath() + "Bugzilla/" + testNumber + "/";
-
-			doMultiUnitTest(CUs, testPath, inputFileBaseNames, null);
-
-		} finally {
-			for (IJavaProject project : proj2PkgRoot.keySet()) {
-				if (project.exists()) {
-					try {
-						project.getProject().delete(true, null);
-					} catch (CoreException e) {
-						// swallow exception to avoid destroying the original one
-						e.printStackTrace();
-					}
-				}
-			}
-		}
-	}
-
-	private ICompilationUnit[] createCUs(String[] inputFileBaseNames, Map<String, IJavaProject> projName2Project, Map<IJavaProject, IPackageFragmentRoot> proj2PkgRoot) throws Exception {
-		ICompilationUnit CUs[]= new ICompilationUnit[inputFileBaseNames.length];
-
-		for(int i= 0; i < inputFileBaseNames.length; i++) {
-			String filePath= inputFileBaseNames[i];
-
-			int projEnd= filePath.indexOf('/');
-			int pkgEnd= filePath.lastIndexOf('/');
-			int fileBegin= pkgEnd+1;
-
-			String projName= filePath.substring(0, projEnd);
-			String pkgName= filePath.substring(projEnd+1, pkgEnd).replace('/', '.');
-
-			IJavaProject project= projName2Project.get(projName);
-			IPackageFragmentRoot root= proj2PkgRoot.get(project);
-			IPackageFragment pkg= root.getPackageFragment(pkgName);
-
-			CUs[i]= createCUForBugTestCase(project, pkg, filePath.substring(fileBegin), true);
-		}
-		return CUs;
-	}
-
-	private void addProjectDependencies(String[] dependencies, Map<String, IJavaProject> projName2Project) throws JavaModelException {
-		for (String dependency : dependencies) {
-			// dependent:provider
-			int colonIdx= dependency.indexOf(':');
-			String depName= dependency.substring(0, colonIdx);
-			String provName= dependency.substring(colonIdx+1);
-			IJavaProject depProj= projName2Project.get(depName);
-			IJavaProject provProj= projName2Project.get(provName);
-			JavaProjectHelper.addRequiredProject(depProj, provProj);
-		}
-	}
-
-	private void createProjectPackageStructure(Map<String, Set<String>> projName2PkgNames, Map<String, IJavaProject> projName2Project, Map<IJavaProject, IPackageFragmentRoot> proj2PkgRoot) throws CoreException, JavaModelException {
-		for (Map.Entry<String, Set<String>> entry : projName2PkgNames.entrySet()) {
-			String projName = entry.getKey();
-			IJavaProject project= JavaProjectHelper.createJavaProject(projName, "bin");
-			IPackageFragmentRoot root= JavaProjectHelper.addSourceContainer(project, CONTAINER);
-			JavaProjectHelper.addRTJar(project);
-			Set<IPackageFragment> pkgs= new HashSet<>();
-			projName2Project.put(projName, project);
-			proj2PkgRoot.put(project, root);
-			for (String pkgName : entry.getValue()) {
-				pkgs.add(root.createPackageFragment(pkgName, true, null));
-			}
-		}
-	}
-
-	private Map<String, Set<String>> collectProjectPackages(String[] inputFileBaseNames) {
-		Map<String, Set<String>> proj2Pkgs= new HashMap<>();
-
-		for (String filePath : inputFileBaseNames) {
-			int projEnd= filePath.indexOf('/');
-			String projName= filePath.substring(0, projEnd);
-			String pkgName= filePath.substring(projEnd+1, filePath.lastIndexOf('/'));
-
-			Set<String> projPkgs= proj2Pkgs.get(projName);
-
-			if (projPkgs == null)
-				proj2Pkgs.put(projName, projPkgs= new HashSet<>());
-			projPkgs.add(pkgName);
-		}
-		return proj2Pkgs;
-	}
-
-	private void failHelper(int expectedStatus) throws Exception {
-		ICompilationUnit	cu= createCUForSimpleTest(getPackageP(), false, true);
-		ISourceRange		selection= findSelectionInSource(cu.getSource());
-		IntroduceFactoryRefactoring	ref= new IntroduceFactoryRefactoring(cu, selection.getOffset(), selection.getLength());
-		RefactoringStatus	result= performRefactoring(ref);
-
-		assertNotNull("precondition was supposed to fail", result);
-		assertEquals("status", expectedStatus, result.getSeverity());
-	}
-
 	//--- TESTS
 	@Test
 	public void testStaticContext_FFF() throws Exception {
diff --git a/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTests15.java b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTests15.java
new file mode 100644
index 0000000..95c4c2d
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTests15.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 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
+ *******************************************************************************/
+package org.eclipse.jdt.ui.tests.refactoring;
+
+import org.junit.Test;
+
+import org.eclipse.jdt.ui.tests.refactoring.rules.JavaPreviewSetup;
+
+public class IntroduceFactoryTests15 extends IntroduceFactoryTestsBase {
+	private static final String REFACTORING_PATH= "IntroduceFactory/";
+
+	public IntroduceFactoryTests15() {
+		rts= new JavaPreviewSetup();
+	}
+
+	@Override
+	protected String getRefactoringPath() {
+		return REFACTORING_PATH;
+	}
+
+	//--- TESTS
+	@Test
+	public void test568987() throws Exception {
+		singleUnitBugHelper("RecCanConst", false);
+	}
+
+	@Test
+	public void test566943() throws Exception {
+		singleUnitBugHelper("RecCompConst", false);
+	}
+}
diff --git a/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTestsBase.java b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTestsBase.java
new file mode 100644
index 0000000..c29c13c
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/IntroduceFactoryTestsBase.java
@@ -0,0 +1,531 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.jdt.ui.tests.refactoring;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jdt.testplugin.JavaProjectHelper;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.ISourceRange;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.SourceRange;
+
+import org.eclipse.jdt.internal.corext.refactoring.code.IntroduceFactoryRefactoring;
+
+import org.eclipse.jdt.ui.tests.refactoring.rules.Java1d6Setup;
+
+/**
+ * @author rfuhrer@watson.ibm.com
+ */
+public class IntroduceFactoryTestsBase extends GenericRefactoringTest {
+	private static final String REFACTORING_PATH= "IntroduceFactory/";
+
+	public IntroduceFactoryTestsBase() {
+		rts= new Java1d6Setup();
+	}
+
+	@Override
+	protected String getRefactoringPath() {
+		return REFACTORING_PATH;
+	}
+
+	/**
+	 * Produces a test file name based on the name of this JUnit testcase.
+	 * For input files, trims off the trailing part of the test name that
+	 * begins with a '_', to get rid of the options part, so that we can
+	 * have a single (suite of) input file(s) but several outputs dependent
+	 * on the option settings.
+	 * @param input true iff the requested file is an input file.
+	 * @return the name of the test file, with a trailing "_in.java" if an input
+	 * file and a trailing "_XXX.java" if an output file and the test name/options
+	 * are "_XXX".
+	 */
+	private String getSimpleTestFileName(boolean input) {
+		String	testName = getName();
+		int		usIdx=  testName.indexOf('_');
+		int		endIdx= (usIdx >= 0) ? usIdx : testName.length();
+		String	fileName = (input ? (testName.substring(4, endIdx) + "_in") : testName.substring(4));
+
+		return fileName + ".java";
+	}
+
+	/**
+	 * Produces a test file name based on the name of this JUnit testcase,
+	 * like getSimpleTestFileName(), but also prepends the appropriate version
+	 * of the resource path (depending on the value of <code>positive</code>).
+	 * Test files are assumed to be located in the resources directory.
+	 * @param positive true iff the requested file is for a positive unit test
+	 * @param input true iff the requested file is an input file
+	 * @return the test file name
+	 */
+	private String getTestFileName(boolean positive, boolean input) {
+		String path= TEST_PATH_PREFIX + getRefactoringPath();
+
+		path += (positive ? "positive/": "negative/");
+		return path + getSimpleTestFileName(input);
+	}
+
+	/**
+	 * Produces a compilation unit from an input source file whose name
+	 * is based on the testcase name.
+	 * Test files are assumed to be located in the resources directory.
+	 * @param pack
+	 * @param positive
+	 * @param input
+	 * @return the ICompilationUnit created from the specified test file
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	private ICompilationUnit createCUForSimpleTest(IPackageFragment pack,
+												  boolean positive, boolean input)
+		throws Exception
+	{
+		String	fileName= getTestFileName(positive, input);
+		String	cuName= getSimpleTestFileName(input);
+
+		return createCU(pack, cuName, getFileContents(fileName));
+	}
+
+	/**
+	 * Produces a test file name based on the name of this JUnit testcase,
+	 * like getSimpleTestFileName(), but also prepends the appropriate version
+	 * of the resource path (depending on the value of <code>positive</code>).
+	 * Test files are assumed to be located in the resources directory.
+	 * @param project the project
+	 * @param pack the package fragment
+	 * @param fileName the file name
+	 * @param input true iff the requested file is an input file
+	 * @return the test file name
+	 */
+	private String getBugTestFileName(IJavaProject project, IPackageFragment pack, String fileName, boolean input) {
+		String testName= getName();
+		String testNumber= testName.substring("test".length());//$NON-NLS-1$
+		String path= TEST_PATH_PREFIX + getRefactoringPath() + "Bugzilla/" + testNumber + "/" +
+									(project == null ? "" : project.getElementName() + "/") +
+									(pack == getPackageP() ? "" : pack.getElementName() + "/");
+
+		return path + fileName + (input ? "" : "_out") + ".java";//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+	}
+
+	/**
+	 * Produces a compilation unit from an input source file whose path
+	 * is based on the testcase name, but whose basename is supplied by
+	 * the caller.
+	 * Test files are assumed to be located in the resources directory.
+	 * @param project can be null if only 1 project exists in the test workspace
+	 * @param pack
+	 * @param baseName
+	 * @param input
+	 * @return the ICompilationUnit created from the specified test file
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	private ICompilationUnit createCUForBugTestCase(IJavaProject project,
+													IPackageFragment pack, String baseName, boolean input)
+		throws Exception
+	{
+		String	fileName= getBugTestFileName(project, pack, baseName, input);
+		String	cuName= baseName + (input ? "" : "_out") + ".java";
+
+		return createCU(pack, cuName, getFileContents(fileName));
+	}
+
+	static final String SELECTION_START_HERALD= "/*[*/";
+	static final String SELECTION_END_HERALD= "/*]*/";
+
+	/**
+	 * Finds and returns the selection markers in the given source string,
+	 * i.e. the first occurrences of <code>SELECTION_START_HERALD</code> and
+	 * <code>SELECTION_END_HERALD</code>. Fails an assertion if either of these
+	 * markers is not present in the source string.
+	 * @param source
+	 * @return an ISourceRange representing the marked selection
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	private ISourceRange findSelectionInSource(String source) throws Exception {
+		int		begin= source.indexOf(SELECTION_START_HERALD) + SELECTION_START_HERALD.length();
+		int		end= source.indexOf(SELECTION_END_HERALD);
+
+		if (begin < SELECTION_START_HERALD.length())
+			fail("No selection start comment in input source file!");
+		if (end < 0)
+			fail("No selection end comment in input source file!");
+
+		return new SourceRange(begin, end-begin);
+	}
+
+	private void doSingleUnitTest(boolean protectConstructor, ICompilationUnit cu, String outputFileName) throws Exception, JavaModelException, IOException {
+		ISourceRange		selection= findSelectionInSource(cu.getSource());
+		IntroduceFactoryRefactoring	ref= new IntroduceFactoryRefactoring(cu, selection.getOffset(), selection.getLength());
+
+		ref.setProtectConstructor(protectConstructor);
+
+		RefactoringStatus	activationResult= ref.checkInitialConditions(new NullProgressMonitor());
+
+		assertTrue("activation was supposed to be successful", activationResult.isOK());
+
+		RefactoringStatus	checkInputResult= ref.checkFinalConditions(new NullProgressMonitor());
+
+		if (!checkInputResult.isOK()) {
+			performChange(ref, false);
+
+			String newSource = cu.getSource();
+
+			System.err.println("!!!Precondition failed for " + getName() + "!!!");
+			System.err.println("Compile-time error: " + checkInputResult.toString());
+			System.err.println("Offending source:");
+			System.err.print(newSource);
+			fail("precondition was supposed to pass but was " + checkInputResult.toString());
+		}
+
+		performChange(ref, false);
+
+		String newSource = cu.getSource();
+
+		assertEqualLines(getName() + ": ", getFileContents(outputFileName), newSource);
+	}
+
+	private void doSingleUnitTestWithWarning(boolean protectConstructor, ICompilationUnit cu, String outputFileName) throws Exception, JavaModelException, IOException {
+		ISourceRange selection= findSelectionInSource(cu.getSource());
+		IntroduceFactoryRefactoring ref= new IntroduceFactoryRefactoring(cu, selection.getOffset(), selection.getLength());
+
+		ref.setProtectConstructor(protectConstructor);
+
+		RefactoringStatus activationResult= ref.checkInitialConditions(new NullProgressMonitor());
+
+		assertTrue("activation was supposed to be successful", activationResult.isOK());
+
+		RefactoringStatus checkInputResult= ref.checkFinalConditions(new NullProgressMonitor());
+
+		assertEquals(RefactoringStatus.WARNING, checkInputResult.getSeverity());
+		performChange(ref, false);
+
+		String newSource= cu.getSource();
+
+		assertEqualLines(getName() + ": ", getFileContents(outputFileName), newSource);
+	}
+
+	/**
+	 * Tests the IntroduceFactoryRefactoring refactoring on a single input source file
+	 * whose name is the test name (minus the "test" prefix and any trailing
+	 * options indicator such as "_FFF"), and compares the transformed code
+	 * to a source file whose name is the test name (minus the "test" prefix).
+	 * Test files are assumed to be located in the resources directory.
+	 * @param protectConstructor true iff IntroduceFactoryRefactoring should make the constructor private
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	void singleUnitHelper(boolean protectConstructor)
+		throws Exception
+	{
+		ICompilationUnit	cu= createCUForSimpleTest(getPackageP(), true, true);
+
+		doSingleUnitTest(protectConstructor, cu, getTestFileName(true, false));
+	}
+
+	/**
+	 * Tests the IntroduceFactoryRefactoring refactoring on a single input source file
+	 * whose name is the test name (minus the "test" prefix and any trailing
+	 * options indicator such as "_FFF"), and compares the transformed code
+	 * to a source file whose name is the test name (minus the "test" prefix).
+	 * Test files are assumed to be located in the resources directory.
+	 * @param baseFileName the base file name
+	 * @param protectConstructor true iff IntroduceFactoryRefactoring should make the constructor private
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	protected void singleUnitBugHelper(String baseFileName, boolean protectConstructor)
+		throws Exception
+	{
+		ICompilationUnit	cu= createCUForBugTestCase(null, getPackageP(), baseFileName, true);
+
+		doSingleUnitTest(protectConstructor, cu, getBugTestFileName(null, getPackageP(), baseFileName, false));
+	}
+
+	protected void singleUnitBugHelperWithWarning(String baseFileName, boolean protectConstructor)
+			throws Exception
+	{
+		ICompilationUnit cu= createCUForBugTestCase(null, getPackageP(), baseFileName, true);
+
+		doSingleUnitTestWithWarning(protectConstructor, cu, getBugTestFileName(null, getPackageP(), baseFileName, false));
+	}
+
+	/**
+	 * Like singleUnitHelper(), but allows for the specification of the names of
+	 * the generated factory method, class, and interface, as appropriate.
+	 * @param factoryMethodName the name to use for the generated factory method
+	 * @param factoryClassName the name of the factory class
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	void namesHelper(String factoryMethodName, String factoryClassName)
+		throws Exception
+	{
+		ICompilationUnit	cu= createCUForSimpleTest(getPackageP(), true, true);
+		ISourceRange		selection= findSelectionInSource(cu.getSource());
+		IntroduceFactoryRefactoring	ref= new IntroduceFactoryRefactoring(cu, selection.getOffset(), selection.getLength());
+
+		RefactoringStatus	activationResult= ref.checkInitialConditions(new NullProgressMonitor());
+
+		assertTrue("activation was supposed to be successful", activationResult.isOK());
+
+		if (factoryMethodName != null)
+			ref.setNewMethodName(factoryMethodName);
+		if (factoryClassName != null)
+			ref.setFactoryClass(factoryClassName);
+
+		RefactoringStatus	checkInputResult= ref.checkFinalConditions(new NullProgressMonitor());
+
+		assertTrue("precondition was supposed to pass but was " + checkInputResult.toString(), checkInputResult.isOK());
+
+		performChange(ref, false);
+
+		String newSource = cu.getSource();
+
+		assertEqualLines(getName() + ": ", getFileContents(getTestFileName(true, false)), newSource);
+	}
+
+	/**
+	 * Creates a compilation unit for a source file with a given base name (plus
+	 * "_in" suffix) in the given package. The source file is assumed to be
+	 * located in the test resources directory.<br>
+	 * Currently only handles positive tests.
+	 * @param fileName the base name of the source file (minus the "_in" suffix)
+	 * @param pack an IPackageFragment for the containing package
+	 * @return the ICompilationUnit for the newly-created unit
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	private ICompilationUnit createCUFromFileName(String fileName, IPackageFragment pack) throws Exception {
+		String fullName = TEST_PATH_PREFIX + getRefactoringPath() + "positive/" + fileName + "_in.java";
+
+		return createCU(pack, fileName + "_in.java", getFileContents(fullName));
+	}
+
+	private void doMultiUnitTest(ICompilationUnit[] CUs, String testPath, String[] outputFileBaseNames, String factoryClassName) throws Exception, JavaModelException, IOException {
+		ISourceRange selection= findSelectionInSource(CUs[0].getSource());
+		IntroduceFactoryRefactoring	ref= new IntroduceFactoryRefactoring(CUs[0], selection.getOffset(), selection.getLength());
+
+		RefactoringStatus activationResult= ref.checkInitialConditions(new NullProgressMonitor());
+
+		assertTrue("activation was supposed to be successful", activationResult.isOK());
+
+		if (factoryClassName != null)
+			ref.setFactoryClass(factoryClassName);
+
+		RefactoringStatus	checkInputResult= ref.checkFinalConditions(new NullProgressMonitor());
+
+		assertTrue("precondition was supposed to pass but was " + checkInputResult.toString(), checkInputResult.isOK());
+
+		performChange(ref, false);
+
+		String	testName= getName();
+
+		for (int i = 0; i < CUs.length; i++) {
+			int optIdx= testName.indexOf("_");
+			String testOptions= (optIdx >= 0) ? testName.substring(optIdx) : "";
+			String outFileName= testPath + outputFileBaseNames[i] + testOptions + "_out.java";
+			String xformedSrc= CUs[i].getSource();
+			String expectedSrc= getFileContents(outFileName);
+
+			assertEqualLines(getName() + ": ", expectedSrc, xformedSrc);
+		}
+	}
+
+	/**
+	 * Tests the IntroduceFactoryRefactoring refactoring on a set of input source files
+	 * whose names are supplied in the <code>fileBaseNames</code> argument,
+	 * and compares the transformed code to source files whose names are
+	 * the input base names plus the options suffix (e.g. "_FFF").
+	 * Test files are assumed to be located in the resources directory.
+	 * @param staticFactoryMethod true iff IntroduceFactoryRefactoring should make the factory method static
+	 * @param inputFileBaseNames an array of input source file base names
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	void multiUnitHelper(boolean staticFactoryMethod, String[] inputFileBaseNames)
+		throws Exception
+	{
+		IPackageFragment	pkg= getPackageP();
+		ICompilationUnit	CUs[]= new ICompilationUnit[inputFileBaseNames.length];
+
+		for (int i = 0; i < inputFileBaseNames.length; i++)
+			CUs[i] = createCUFromFileName(inputFileBaseNames[i], pkg);
+
+		String	testPath= TEST_PATH_PREFIX + getRefactoringPath() + "positive/";
+
+		doMultiUnitTest(CUs, testPath, inputFileBaseNames, null);
+	}
+
+	/**
+	 * Tests the IntroduceFactoryRefactoring refactoring on a set of input source files
+	 * whose names are supplied in the <code>fileBaseNames</code> argument,
+	 * and compares the transformed code to source files whose names are
+	 * the input base names plus the options suffix (e.g. "_FFF").
+	 * Test files are assumed to be located in the resources directory.
+	 * @param staticFactoryMethod true iff IntroduceFactoryRefactoring should make the factory method static
+	 * @param inputFileBaseNames an array of input source file base names
+	 * @param factoryClassName the fully-qualified name of the class to receive the factory method, or null
+	 * if the factory method is to be placed on the class defining the given constructor
+	 * @throws Exception
+	 */
+	@SuppressWarnings("javadoc")
+	void multiUnitBugHelper(boolean staticFactoryMethod, String[] inputFileBaseNames, String factoryClassName)
+		throws Exception
+	{
+		ICompilationUnit CUs[]= new ICompilationUnit[inputFileBaseNames.length];
+
+		for(int i= 0; i < inputFileBaseNames.length; i++) {
+			int pkgEnd= inputFileBaseNames[i].lastIndexOf('/')+1;
+			boolean explicitPkg= (pkgEnd > 0);
+			IPackageFragment pkg= explicitPkg ? getRoot().createPackageFragment(inputFileBaseNames[i].substring(0, pkgEnd-1), true, new NullProgressMonitor()) : getPackageP();
+
+			CUs[i]= createCUForBugTestCase(null, pkg, inputFileBaseNames[i].substring(pkgEnd), true);
+		}
+
+		String	testName= getName();
+		String	testNumber= testName.substring("test".length());
+		String	testPath= TEST_PATH_PREFIX + getRefactoringPath() + "Bugzilla/" + testNumber + "/";
+
+		doMultiUnitTest(CUs, testPath, inputFileBaseNames, factoryClassName);
+	}
+
+	void multiProjectBugHelper(String[] inputFileBaseNames, String[] dependencies) throws Exception {
+		Map<String, Set<String>> projName2PkgNames= collectProjectPackages(inputFileBaseNames);
+		Map<String, IJavaProject> projName2Project= new HashMap<>();
+		Map<IJavaProject, IPackageFragmentRoot> proj2PkgRoot= new HashMap<>();
+
+		try {
+			createProjectPackageStructure(projName2PkgNames, projName2Project, proj2PkgRoot);
+
+			ICompilationUnit[] CUs= createCUs(inputFileBaseNames, projName2Project, proj2PkgRoot);
+
+			addProjectDependencies(dependencies, projName2Project);
+
+			String testName= getName();
+			String testNumber= testName.substring("test".length());
+			String testPath= TEST_PATH_PREFIX + getRefactoringPath() + "Bugzilla/" + testNumber + "/";
+
+			doMultiUnitTest(CUs, testPath, inputFileBaseNames, null);
+
+		} finally {
+			for (IJavaProject project : proj2PkgRoot.keySet()) {
+				if (project.exists()) {
+					try {
+						project.getProject().delete(true, null);
+					} catch (CoreException e) {
+						// swallow exception to avoid destroying the original one
+						e.printStackTrace();
+					}
+				}
+			}
+		}
+	}
+
+	private ICompilationUnit[] createCUs(String[] inputFileBaseNames, Map<String, IJavaProject> projName2Project, Map<IJavaProject, IPackageFragmentRoot> proj2PkgRoot) throws Exception {
+		ICompilationUnit CUs[]= new ICompilationUnit[inputFileBaseNames.length];
+
+		for(int i= 0; i < inputFileBaseNames.length; i++) {
+			String filePath= inputFileBaseNames[i];
+
+			int projEnd= filePath.indexOf('/');
+			int pkgEnd= filePath.lastIndexOf('/');
+			int fileBegin= pkgEnd+1;
+
+			String projName= filePath.substring(0, projEnd);
+			String pkgName= filePath.substring(projEnd+1, pkgEnd).replace('/', '.');
+
+			IJavaProject project= projName2Project.get(projName);
+			IPackageFragmentRoot root= proj2PkgRoot.get(project);
+			IPackageFragment pkg= root.getPackageFragment(pkgName);
+
+			CUs[i]= createCUForBugTestCase(project, pkg, filePath.substring(fileBegin), true);
+		}
+		return CUs;
+	}
+
+	private void addProjectDependencies(String[] dependencies, Map<String, IJavaProject> projName2Project) throws JavaModelException {
+		for (String dependency : dependencies) {
+			// dependent:provider
+			int colonIdx= dependency.indexOf(':');
+			String depName= dependency.substring(0, colonIdx);
+			String provName= dependency.substring(colonIdx+1);
+			IJavaProject depProj= projName2Project.get(depName);
+			IJavaProject provProj= projName2Project.get(provName);
+			JavaProjectHelper.addRequiredProject(depProj, provProj);
+		}
+	}
+
+	private void createProjectPackageStructure(Map<String, Set<String>> projName2PkgNames, Map<String, IJavaProject> projName2Project, Map<IJavaProject, IPackageFragmentRoot> proj2PkgRoot) throws CoreException, JavaModelException {
+		for (Map.Entry<String, Set<String>> entry : projName2PkgNames.entrySet()) {
+			String projName = entry.getKey();
+			IJavaProject project= JavaProjectHelper.createJavaProject(projName, "bin");
+			IPackageFragmentRoot root= JavaProjectHelper.addSourceContainer(project, CONTAINER);
+			JavaProjectHelper.addRTJar(project);
+			Set<IPackageFragment> pkgs= new HashSet<>();
+			projName2Project.put(projName, project);
+			proj2PkgRoot.put(project, root);
+			for (String pkgName : entry.getValue()) {
+				pkgs.add(root.createPackageFragment(pkgName, true, null));
+			}
+		}
+	}
+
+	private Map<String, Set<String>> collectProjectPackages(String[] inputFileBaseNames) {
+		Map<String, Set<String>> proj2Pkgs= new HashMap<>();
+
+		for (String filePath : inputFileBaseNames) {
+			int projEnd= filePath.indexOf('/');
+			String projName= filePath.substring(0, projEnd);
+			String pkgName= filePath.substring(projEnd+1, filePath.lastIndexOf('/'));
+
+			Set<String> projPkgs= proj2Pkgs.get(projName);
+
+			if (projPkgs == null)
+				proj2Pkgs.put(projName, projPkgs= new HashSet<>());
+			projPkgs.add(pkgName);
+		}
+		return proj2Pkgs;
+	}
+
+	protected void failHelper(int expectedStatus) throws Exception {
+		ICompilationUnit	cu= createCUForSimpleTest(getPackageP(), false, true);
+		ISourceRange		selection= findSelectionInSource(cu.getSource());
+		IntroduceFactoryRefactoring	ref= new IntroduceFactoryRefactoring(cu, selection.getOffset(), selection.getLength());
+		RefactoringStatus	result= performRefactoring(ref);
+
+		assertNotNull("precondition was supposed to fail", result);
+		assertEquals("status", expectedStatus, result.getSeverity());
+	}
+}
diff --git a/org.eclipse.jdt.ui/.settings/.api_filters b/org.eclipse.jdt.ui/.settings/.api_filters
index 90c76a9..01b5436 100644
--- a/org.eclipse.jdt.ui/.settings/.api_filters
+++ b/org.eclipse.jdt.ui/.settings/.api_filters
@@ -63,6 +63,20 @@
                 <message_argument value="isCanonicalConstructor()"/>
             </message_arguments>
         </filter>
+        <filter comment="This is a Java 15 preview feature" id="640712815">
+            <message_arguments>
+                <message_argument value="IMethodBinding"/>
+                <message_argument value="IntroduceFactoryRefactoring"/>
+                <message_argument value="isCompactConstructor()"/>
+            </message_arguments>
+        </filter>
+        <filter comment="This is a Java 15 preview feature" id="640712815">
+            <message_arguments>
+                <message_argument value="RecordDeclaration"/>
+                <message_argument value="IntroduceFactoryRefactoring"/>
+                <message_argument value="recordComponents()"/>
+            </message_arguments>
+        </filter>
     </resource>
     <resource path="core refactoring/org/eclipse/jdt/internal/corext/refactoring/code/ReplaceInvocationsRefactoring.java" type="org.eclipse.jdt.internal.corext.refactoring.code.ReplaceInvocationsRefactoring">
         <filter id="571519004">
diff --git a/org.eclipse.jdt.ui/core refactoring/org/eclipse/jdt/internal/corext/refactoring/code/IntroduceFactoryRefactoring.java b/org.eclipse.jdt.ui/core refactoring/org/eclipse/jdt/internal/corext/refactoring/code/IntroduceFactoryRefactoring.java
index 979ee68..97c3575 100644
--- a/org.eclipse.jdt.ui/core refactoring/org/eclipse/jdt/internal/corext/refactoring/code/IntroduceFactoryRefactoring.java
+++ b/org.eclipse.jdt.ui/core refactoring/org/eclipse/jdt/internal/corext/refactoring/code/IntroduceFactoryRefactoring.java
@@ -66,6 +66,7 @@
 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;
@@ -579,7 +580,13 @@
 		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();