Bug 569336 - Support custom formatting options at compilation unit level

- Add new setter and getter to the ICompilationUnit to configure
custom formatting options
- Save the custom options just in memory but not persist them in
the preference store
- Adopt the new API to fetch the formatting options from the
ICompilationUnit when rewriting AST

Change-Id: I1ecf9d7c673bb452b4e135fdbad29010c7f0b44e
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompilationUnitTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompilationUnitTests.java
index 0ae0759..7692f35 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompilationUnitTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompilationUnitTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2020 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,6 +14,7 @@
  *     Pierre-Yves B. <pyvesdev@gmail.com> - Contributions for
  *                              Bug 559618 - No compiler warning for import from same package
  *                              Bug 560630 - No warning on unused import on class from same package
+ *     Microsoft Corporation - support custom options at compilation unit level
  *******************************************************************************/
 package org.eclipse.jdt.core.tests.model;
 
@@ -22,6 +23,8 @@
 import java.net.URISyntaxException;
 
 import junit.framework.Test;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
@@ -34,6 +37,7 @@
 import org.eclipse.jdt.core.dom.AST;
 import org.eclipse.jdt.core.dom.ASTParser;
 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
 import org.eclipse.jdt.internal.core.Buffer;
 import org.eclipse.jdt.internal.core.CompilationUnit;
 import org.eclipse.jdt.internal.core.util.Util;
@@ -2818,4 +2822,43 @@
           deleteFile("/P/src/p/D.java");
   }
 }
+
+public void testSetOptions() throws CoreException {
+	try {
+		Map<String, String> newOptions = new HashMap<>();
+		newOptions.put(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "4");
+		newOptions.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE);
+		this.cu.setOptions(newOptions);
+
+		Map<String, String> customOptions = this.cu.getCustomOptions();
+		assertEquals(newOptions.size(), customOptions.size());
+		for (Map.Entry<String, String> entry : newOptions.entrySet()) {
+			assertEquals(entry.getValue(), customOptions.get(entry.getKey()));
+		}
+	} finally {
+		this.cu.setOptions(null);
+	}
+}
+
+public void testGetOptions() throws CoreException {
+	String oldTabChar = this.testProject.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, false);
+	try {
+		Map<String, String> projectOptions = this.testProject.getOptions(true);
+		Map<String, String> unitOptions = this.cu.getOptions(true);
+		assertEquals("Should inherit the project options", unitOptions, projectOptions);
+
+		this.testProject.setOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.TAB);
+		Map<String, String> newOptions = new HashMap<>();
+		newOptions.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE);
+		this.cu.setOptions(newOptions);
+
+		projectOptions = this.testProject.getOptions(true);
+		unitOptions = this.cu.getOptions(true);
+		assertEquals("Should use the options from the compilation unit", JavaCore.SPACE, unitOptions.get(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR));
+		assertEquals("should inherit the project options", projectOptions.get(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE), unitOptions.get(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE));
+	} finally {
+		this.testProject.setOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, oldTabChar);
+		this.cu.setOptions(null);
+	}
+}
 }
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ASTRewrite.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ASTRewrite.java
index b5a2f12..49a8617 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ASTRewrite.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ASTRewrite.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2014 IBM Corporation and others.
+ * Copyright (c) 2004, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Microsoft Corporation - read formatting options from the compilation unit
  *******************************************************************************/
 package org.eclipse.jdt.core.dom.rewrite;
 
@@ -289,7 +290,8 @@
 		char[] content= typeRoot.getBuffer().getCharacters();
 		LineInformation lineInfo= LineInformation.create(astRoot);
 		String lineDelim= typeRoot.findRecommendedLineSeparator();
-		Map options= typeRoot.getJavaProject().getOptions(true);
+		Map options= typeRoot instanceof ICompilationUnit ? ((ICompilationUnit) typeRoot).getOptions(true) :
+			typeRoot.getJavaProject().getOptions(true);
 
 		return internalRewriteAST(content, lineInfo, lineDelim, astRoot.getCommentList(), options, rootNode, (RecoveryScannerData)astRoot.getStatementsRecoveryData());
 	}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/ICompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/ICompilationUnit.java
index 0507a4d..8322638 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/ICompilationUnit.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/ICompilationUnit.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2019 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -11,9 +11,13 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     IBM Corporation - added J2SE 1.5 support
+ *     Microsoft Corporation - support custom options at compilation unit level
  *******************************************************************************/
 package org.eclipse.jdt.core;
 
+import java.util.Collections;
+import java.util.Map;
+
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.core.dom.ASTParser;
 import org.eclipse.jdt.core.dom.CompilationUnit;
@@ -558,6 +562,56 @@
 boolean isWorkingCopy();
 
 /**
+ * Sets the ICompilationUnit custom options. All and only the options explicitly included in the given table
+ * are remembered; all previous option settings are forgotten, including ones not explicitly
+ * mentioned.
+ * <p>
+ * For a complete description of the configurable options, see <code>JavaCore#getDefaultOptions</code>.
+ * </p>
+ *
+ * @param newOptions the new custom options for this compilation unit
+ * @see JavaCore#setOptions(java.util.Hashtable)
+ * @since 3.25
+ */
+default void setOptions(Map<String, String> newOptions) {
+}
+
+/**
+ * Returns the table of the current custom options for this ICompilationUnit. If there is no <code>setOptions</code> called
+ * for the ICompliationUnit, then return an empty table.
+ *
+ * @return the table of the current custom options for this ICompilationUnit
+ * @since 3.25
+ */
+default Map<String, String> getCustomOptions() {
+	return Collections.emptyMap();
+}
+
+/**
+ * Returns the table of the options for this ICompilationUnit, which includes its custom options and options
+ * inherited from its parent JavaProject. The boolean argument <code>inheritJavaCoreOptions</code> allows
+ * to directly merge the global ones from <code>JavaCore</code>.
+ * <p>
+ * For a complete description of the configurable options, see <code>JavaCore#getDefaultOptions</code>.
+ * </p>
+ *
+ * @param inheritJavaCoreOptions - boolean indicating whether the JavaCore options should be inherited as well
+ * @return table of current settings of all options
+ * @see JavaCore#getDefaultOptions()
+ * @since 3.25
+ */
+default Map<String, String> getOptions(boolean inheritJavaCoreOptions) {
+	IJavaProject parentProject = getJavaProject();
+	Map<String, String> options = parentProject == null ? JavaCore.getOptions() : parentProject.getOptions(inheritJavaCoreOptions);
+	Map<String, String> customOptions = getCustomOptions();
+	if (customOptions != null) {
+		options.putAll(customOptions);
+	}
+
+	return options;
+}
+
+/**
  * Reconciles the contents of this working copy, sends out a Java delta
  * notification indicating the nature of the change of the working copy since
  * the last time it was either reconciled or made consistent
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java
index cbdaac5..9d45254 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -12,12 +12,13 @@
  *     IBM Corporation - initial API and implementation
  *     Alex Smirnoff (alexsmr@sympatico.ca) - part of the changes to support Java-like extension
  *                                                            (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=71460)
+ *     Microsoft Corporation - support custom options at compilation unit level
  *******************************************************************************/
 package org.eclipse.jdt.internal.core;
 
 import java.io.IOException;
 import java.util.*;
-
+import java.util.concurrent.ConcurrentHashMap;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.jdt.core.*;
@@ -137,7 +138,7 @@
 
 	boolean computeProblems = perWorkingCopyInfo != null && perWorkingCopyInfo.isActive() && project != null && JavaProject.hasJavaNature(project.getProject());
 	IProblemFactory problemFactory = new DefaultProblemFactory();
-	Map options = project == null ? JavaCore.getOptions() : project.getOptions(true);
+	Map options = this.getOptions(true);
 	if (!computeProblems) {
 		// disable task tags checking to speed up parsing
 		options.put(JavaCore.COMPILER_TASK_TAGS, ""); //$NON-NLS-1$
@@ -1446,4 +1447,30 @@
 	}
 	return null;
 }
+
+@Override
+public void setOptions(Map<String, String> newOptions) {
+	Map<String, String> customOptions = newOptions == null ? null : new ConcurrentHashMap<String, String>(newOptions);
+	try {
+		this.getCompilationUnitElementInfo().setCustomOptions(customOptions);
+	} catch (JavaModelException e) {
+		// do nothing
+	}
+}
+
+@Override
+public Map<String, String> getCustomOptions() {
+	try {
+		Map<String, String> customOptions = this.getCompilationUnitElementInfo().getCustomOptions();
+		return customOptions == null ? Collections.emptyMap() : customOptions;
+	} catch (JavaModelException e) {
+		// do nothing
+	}
+
+	return Collections.emptyMap();
+}
+
+private CompilationUnitElementInfo getCompilationUnitElementInfo() throws JavaModelException {
+	return (CompilationUnitElementInfo) this.getElementInfo();
+}
 }
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitElementInfo.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitElementInfo.java
index c2b6ec8..1d8e12d 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitElementInfo.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitElementInfo.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,9 +10,12 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Microsoft Corporation - support custom options at compilation unit level
  *******************************************************************************/
 package org.eclipse.jdt.internal.core;
 
+import java.util.Map;
+
 import org.eclipse.jdt.core.ISourceRange;
 import org.eclipse.jdt.core.SourceRange;
 
@@ -41,6 +44,11 @@
 
 	public boolean hasFunctionalTypes = false;
 
+	/**
+	 * The custom options for this compilation unit
+	 */
+	private Map<String, String> customOptions;
+
 /**
  * Returns the length of the source string.
  */
@@ -56,4 +64,18 @@
 public void setSourceLength(int newSourceLength) {
 	this.sourceLength = newSourceLength;
 }
+
+/**
+ * Returns the custom options of this compilation unit element.
+ */
+public synchronized Map<String, String> getCustomOptions() {
+	return this.customOptions;
+}
+
+/**
+ * Sets the custom options of this compilation unit element.
+ */
+public synchronized void setCustomOptions(Map<String, String> customOptions) {
+	this.customOptions = customOptions;
+}
 }
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SortElementsOperation.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SortElementsOperation.java
index d1c6e9b..8032421 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SortElementsOperation.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SortElementsOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -11,6 +11,7 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Alex Blewitt - alex_blewitt@yahoo.com https://bugs.eclipse.org/bugs/show_bug.cgi?id=171066
+ *     Microsoft Corporation - read formatting options from the compilation unit
  *******************************************************************************/
 package org.eclipse.jdt.internal.core;
 
@@ -142,7 +143,7 @@
 			}
 
 			Document document= new Document(content);
-			return rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
+			return rewrite.rewriteAST(document, cu.getOptions(true));
 		} finally {
 			done();
 		}