Bug 530756 - [formatter] Align fields in columns: add option to use spaces

Change-Id: I44937e3808bad8de32ee499cde76f47e01fc2156
Signed-off-by: Mateusz Matela <mateusz.matela@gmail.com>
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
index 687a381..7640b4b 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
@@ -14259,4 +14259,54 @@
 	formatSource(input, getCompilationUnit("Formatter", "", "test104910", "G_out.java").getSource());
 }
 
+/**
+ * https://bugs.eclipse.org/530756 - [formatter] Align fields in columns: add option to use spaces
+ */
+public void testBug530756a() throws JavaModelException {
+	this.formatterPrefs.tab_char = DefaultCodeFormatterOptions.TAB;
+	this.formatterPrefs.align_type_members_on_columns = true;
+	String input = getCompilationUnit("Formatter", "", "test530756", "in.java").getSource();
+	formatSource(input, getCompilationUnit("Formatter", "", "test530756", "A_out.java").getSource());
+}
+/**
+ * https://bugs.eclipse.org/530756 - [formatter] Align fields in columns: add option to use spaces
+ */
+public void testBug530756b() throws JavaModelException {
+	this.formatterPrefs.tab_char = DefaultCodeFormatterOptions.TAB;
+	this.formatterPrefs.align_type_members_on_columns = true;
+	this.formatterPrefs.align_with_spaces = true;
+	String input = getCompilationUnit("Formatter", "", "test530756", "in.java").getSource();
+	formatSource(input, getCompilationUnit("Formatter", "", "test530756", "B_out.java").getSource());
+}
+/**
+ * https://bugs.eclipse.org/530756 - [formatter] Align fields in columns: add option to use spaces
+ */
+public void testBug530756c() throws JavaModelException {
+	this.formatterPrefs.tab_char = DefaultCodeFormatterOptions.MIXED;
+	this.formatterPrefs.indentation_size = 6;
+	this.formatterPrefs.align_type_members_on_columns = true;
+	this.formatterPrefs.align_with_spaces = true;
+	String input = getCompilationUnit("Formatter", "", "test530756", "in.java").getSource();
+	formatSource(input, getCompilationUnit("Formatter", "", "test530756", "C_out.java").getSource());
+}
+/**
+ * https://bugs.eclipse.org/530756 - [formatter] Align fields in columns: add option to use spaces
+ */
+public void testBug530756d() throws JavaModelException {
+	this.formatterPrefs.tab_char = DefaultCodeFormatterOptions.SPACE;
+	this.formatterPrefs.align_type_members_on_columns = true;
+	this.formatterPrefs.align_with_spaces = true;
+	String input = getCompilationUnit("Formatter", "", "test530756", "in.java").getSource();
+	formatSource(input, getCompilationUnit("Formatter", "", "test530756", "D_out.java").getSource());
+}
+/**
+ * https://bugs.eclipse.org/530756 - [formatter] Align fields in columns: add option to use spaces
+ */
+public void testBug530756e() throws JavaModelException {
+	this.formatterPrefs.tab_char = DefaultCodeFormatterOptions.SPACE;
+	this.formatterPrefs.align_type_members_on_columns = true;
+	this.formatterPrefs.align_with_spaces = false;
+	String input = getCompilationUnit("Formatter", "", "test530756", "in.java").getSource();
+	formatSource(input, getCompilationUnit("Formatter", "", "test530756", "E_out.java").getSource());
+}
 }
diff --git a/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/A_out.java b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/A_out.java
new file mode 100644
index 0000000..79949c3
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/A_out.java
@@ -0,0 +1,5 @@
+class C {
+	public int		a		= 5;
+	String			ssssss	= "1234567890";	// comment 1
+	private boolean	bbbbb	= true;			// comment 2
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/B_out.java b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/B_out.java
new file mode 100644
index 0000000..c1675b9
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/B_out.java
@@ -0,0 +1,5 @@
+class C {
+	public int      a      = 5;
+	String          ssssss = "1234567890"; // comment 1
+	private boolean bbbbb  = true;         // comment 2
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/C_out.java b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/C_out.java
new file mode 100644
index 0000000..2fdfab0
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/C_out.java
@@ -0,0 +1,5 @@
+class C {
+	  public int      a      = 5;
+	  String          ssssss = "1234567890"; // comment 1
+	  private boolean bbbbb  = true;         // comment 2
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/D_out.java b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/D_out.java
new file mode 100644
index 0000000..25cfe09
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/D_out.java
@@ -0,0 +1,5 @@
+class C {
+    public int      a      = 5;
+    String          ssssss = "1234567890"; // comment 1
+    private boolean bbbbb  = true;         // comment 2
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/E_out.java b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/E_out.java
new file mode 100644
index 0000000..25cfe09
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/E_out.java
@@ -0,0 +1,5 @@
+class C {
+    public int      a      = 5;
+    String          ssssss = "1234567890"; // comment 1
+    private boolean bbbbb  = true;         // comment 2
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/in.java b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/in.java
new file mode 100644
index 0000000..20f5bf1
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/Formatter/test530756/in.java
@@ -0,0 +1,5 @@
+class C {
+	public int a = 5;
+	String ssssss = "1234567890"; // comment 1
+	private boolean bbbbb = true; // comment 2
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
index 93c217f..1bf0cd4 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
@@ -58,7 +58,7 @@
 	/**
 	 * <pre>
 	 * FORMATTER / Option to align type members of a type declaration on column
-	 *     - option id:         "org.eclipse.jdt.core.formatter.formatter.align_type_members_on_columns"
+	 *     - option id:         "org.eclipse.jdt.core.formatter.align_type_members_on_columns"
 	 *     - possible values:   { TRUE, FALSE }
 	 *     - default:           FALSE
 	 * </pre>
@@ -70,6 +70,19 @@
 
 	/**
 	 * <pre>
+	 * FORMATTER / Option to use spaces when aligning members, independent of selected tabulation character
+	 *     - option id:         "org.eclipse.jdt.core.formatter.align_with_spaces"
+	 *     - possible values:   { TRUE, FALSE }
+	 *     - default:           FALSE
+	 * </pre>
+	 * @see #TRUE
+	 * @see #FALSE
+	 * @since 3.15
+	 */
+	public static final String FORMATTER_ALIGN_WITH_SPACES = JavaCore.PLUGIN_ID + ".formatter.align_with_spaces";	 //$NON-NLS-1$
+
+	/**
+	 * <pre>
 	 * FORMATTER / Option to align groups of members independently if they are separated by a certain number of blank lines
 	 *     - option id:         "org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines"
 	 *     - possible values:   "&lt;n&gt;", where n is a positive integer
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
index 2c8705a..5f416ea 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
@@ -143,6 +143,7 @@
 	public int alignment_for_union_type_in_multicatch;
 
 	public boolean align_type_members_on_columns;
+	public boolean align_with_spaces;
 	public int align_fields_grouping_blank_lines;
 
 	public String brace_position_for_annotation_type_declaration;
@@ -485,6 +486,7 @@
 		options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_UNION_TYPE_IN_MULTICATCH, getAlignment(this.alignment_for_union_type_in_multicatch));
 		options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGN_TYPE_MEMBERS_ON_COLUMNS, this.align_type_members_on_columns ? DefaultCodeFormatterConstants.TRUE : DefaultCodeFormatterConstants.FALSE);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGN_FIELDS_GROUPING_BLANK_LINES, Integer.toString(this.align_fields_grouping_blank_lines));
+		options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGN_WITH_SPACES, this.align_with_spaces ? DefaultCodeFormatterConstants.TRUE : DefaultCodeFormatterConstants.FALSE);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ANNOTATION_TYPE_DECLARATION, this.brace_position_for_annotation_type_declaration);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ANONYMOUS_TYPE_DECLARATION, this.brace_position_for_anonymous_type_declaration);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER, this.brace_position_for_array_initializer);
@@ -1055,6 +1057,10 @@
 				this.align_fields_grouping_blank_lines = Integer.MAX_VALUE;
 			}
 		}
+		final Object alignWithSpaces = settings.get(DefaultCodeFormatterConstants.FORMATTER_ALIGN_WITH_SPACES);
+		if (alignWithSpaces != null) {
+			this.align_with_spaces = DefaultCodeFormatterConstants.TRUE.equals(alignWithSpaces);
+		}
 		final Object bracePositionForAnnotationTypeDeclarationOption = settings.get(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ANNOTATION_TYPE_DECLARATION);
 		if (bracePositionForAnnotationTypeDeclarationOption != null) {
 			try {
@@ -2489,6 +2495,7 @@
 		this.alignment_for_type_parameters = Alignment.M_NO_ALIGNMENT;
 		this.alignment_for_union_type_in_multicatch = Alignment.M_COMPACT_SPLIT;
 		this.align_type_members_on_columns = false;
+		this.align_with_spaces = false;
 		this.align_fields_grouping_blank_lines = Integer.MAX_VALUE;
 		this.brace_position_for_annotation_type_declaration = DefaultCodeFormatterConstants.END_OF_LINE;
 		this.brace_position_for_anonymous_type_declaration = DefaultCodeFormatterConstants.END_OF_LINE;
@@ -2806,6 +2813,7 @@
 		this.alignment_for_type_parameters = Alignment.M_NO_ALIGNMENT;
 		this.alignment_for_union_type_in_multicatch = Alignment.M_COMPACT_SPLIT;
 		this.align_type_members_on_columns = false;
+		this.align_with_spaces = false;
 		this.align_fields_grouping_blank_lines = Integer.MAX_VALUE;
 		this.brace_position_for_annotation_type_declaration = DefaultCodeFormatterConstants.END_OF_LINE;
 		this.brace_position_for_anonymous_type_declaration = DefaultCodeFormatterConstants.END_OF_LINE;
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java
index 060df8d..28dfdc1 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java
@@ -58,7 +58,7 @@
 		this.options = options;
 		this.regions = adaptRegions(regions);
 
-		this.alignChar = this.options.tab_char;
+		this.alignChar = this.options.align_with_spaces ? DefaultCodeFormatterOptions.SPACE : this.options.tab_char;
 		this.sourceLimit = source.length();
 		this.parent = null;
 
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/FieldAligner.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/FieldAligner.java
index 9af0fab..3e0863e 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/FieldAligner.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/FieldAligner.java
@@ -121,7 +121,7 @@
 			int positionInLine = this.tm.getPositionInLine(nameIndex);
 			maxNameAlign = Math.max(maxNameAlign, positionInLine);
 		}
-		maxNameAlign = this.tm.toIndent(maxNameAlign, false);
+		maxNameAlign = normalizedAlign(maxNameAlign);
 
 		int maxAssignAlign = 0;
 		for (FieldDeclaration declaration : alignGroup) {
@@ -138,7 +138,7 @@
 				maxAssignAlign = Math.max(maxAssignAlign, positionInLine);
 			}
 		}
-		maxAssignAlign = this.tm.toIndent(maxAssignAlign, false);
+		maxAssignAlign = normalizedAlign(maxAssignAlign);
 
 		for (FieldDeclaration declaration : alignGroup) {
 			List<VariableDeclarationFragment> fragments = declaration.fragments();
@@ -166,7 +166,7 @@
 				maxCommentAlign = Math.max(maxCommentAlign,
 						positionCounter.findMaxPosition(firstIndexInLine, lastIndex));
 			}
-			maxCommentAlign = this.tm.toIndent(maxCommentAlign, false);
+			maxCommentAlign = normalizedAlign(maxCommentAlign);
 
 			for (FieldDeclaration declaration : alignGroup) {
 				int typeIndex = this.tm.firstIndexIn(declaration.getType(), -1);
@@ -191,4 +191,10 @@
 			}
 		}
 	}
+
+	private int normalizedAlign(int desiredAlign) {
+		if (this.options.align_with_spaces)
+			return desiredAlign;
+		return this.tm.toIndent(desiredAlign, false);
+	}
 }