diff --git a/jdt-patch/e49/Feature-org.codehaus.groovy.jdt.patch/feature.xml b/jdt-patch/e49/Feature-org.codehaus.groovy.jdt.patch/feature.xml index 026109a48d..393d09cb78 100644 --- a/jdt-patch/e49/Feature-org.codehaus.groovy.jdt.patch/feature.xml +++ b/jdt-patch/e49/Feature-org.codehaus.groovy.jdt.patch/feature.xml @@ -18,7 +18,7 @@ - + * 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 * @@ -70,7 +70,49 @@ public class DefaultCodeFormatterConstants { /** *
-	 * FORMATTER / Option to align groups of members independently if they are separated by a certain number of blank lines
+	 * FORMATTER / Option to align variable declarations on column
+	 *     - option id:         "org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns"
+	 *     - possible values:   { TRUE, FALSE }
+	 *     - default:           FALSE
+	 * 
+ * @see #TRUE + * @see #FALSE + * @since 3.15 + */ + public static final String FORMATTER_ALIGN_VARIABLE_DECLARATIONS_ON_COLUMNS = JavaCore.PLUGIN_ID + ".formatter.align_variable_declarations_on_columns"; //$NON-NLS-1$ + + /** + *
+	 * FORMATTER / Option to align assignment statements on column
+	 *     - option id:         "org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns"
+	 *     - possible values:   { TRUE, FALSE }
+	 *     - default:           FALSE
+	 * 
+ * @see #TRUE + * @see #FALSE + * @since 3.15 + */ + public static final String FORMATTER_ALIGN_ASSIGNMENT_STATEMENTS_ON_COLUMNS = JavaCore.PLUGIN_ID + ".formatter.align_assignment_statements_on_columns"; //$NON-NLS-1$ + + /** + *
+	 * 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
+	 * 
+ * @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$ + + /** + *
+	 * FORMATTER / Option to affect aligning on columns: groups of items are aligned independently
+	 * if they are separated by at least the selected number of blank lines.
+	 * Note: since 3.15 the 'fields' part is a (potentially misleading) residue as this option
+	 * affects other types of aligning on columns as well.
 	 *     - option id:         "org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines"
 	 *     - possible values:   "<n>", where n is a positive integer
 	 *     - default:           {@code Integer.MAX_VALUE}
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
index 2c8705a227..92f65e93c5 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
+++ b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
@@ -143,6 +143,9 @@ public static DefaultCodeFormatterOptions getJavaConventionsSettings() {
 	public int alignment_for_union_type_in_multicatch;
 
 	public boolean align_type_members_on_columns;
+	public boolean align_variable_declarations_on_columns;
+	public boolean align_assignment_statements_on_columns;
+	public boolean align_with_spaces;
 	public int align_fields_grouping_blank_lines;
 
 	public String brace_position_for_annotation_type_declaration;
@@ -484,7 +487,10 @@ public Map getMap() {
 		options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_TYPE_PARAMETERS, getAlignment(this.alignment_for_type_parameters));
 		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_VARIABLE_DECLARATIONS_ON_COLUMNS, this.align_variable_declarations_on_columns ? DefaultCodeFormatterConstants.TRUE : DefaultCodeFormatterConstants.FALSE);
+		options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGN_ASSIGNMENT_STATEMENTS_ON_COLUMNS, this.align_assignment_statements_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);
@@ -1045,6 +1051,14 @@ public void set(Map settings) {
 		if (alignTypeMembersOnColumnsOption != null) {
 			this.align_type_members_on_columns = DefaultCodeFormatterConstants.TRUE.equals(alignTypeMembersOnColumnsOption);
 		}
+		final Object alignVariableDeclarationsOnColumnsOption = settings.get(DefaultCodeFormatterConstants.FORMATTER_ALIGN_VARIABLE_DECLARATIONS_ON_COLUMNS);
+		if (alignVariableDeclarationsOnColumnsOption != null) {
+			this.align_variable_declarations_on_columns = DefaultCodeFormatterConstants.TRUE.equals(alignVariableDeclarationsOnColumnsOption);
+		}
+		final Object alignAssignmentStatementsOnColumnsOption = settings.get(DefaultCodeFormatterConstants.FORMATTER_ALIGN_ASSIGNMENT_STATEMENTS_ON_COLUMNS);
+		if (alignAssignmentStatementsOnColumnsOption != null) {
+			this.align_assignment_statements_on_columns = DefaultCodeFormatterConstants.TRUE.equals(alignAssignmentStatementsOnColumnsOption);
+		}
 		final Object alignGroupSepartionBlankLinesOption = settings.get(DefaultCodeFormatterConstants.FORMATTER_ALIGN_FIELDS_GROUPING_BLANK_LINES);
 		if (alignTypeMembersOnColumnsOption != null) {
 			try {
@@ -1055,6 +1069,10 @@ public void set(Map settings) {
 				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 +2507,9 @@ public void setDefaultSettings() {
 		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_variable_declarations_on_columns = false;
+		this.align_assignment_statements_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 +2827,9 @@ public void setJavaConventionsSettings() {
 		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_variable_declarations_on_columns = false;
+		this.align_assignment_statements_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/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java
index 060df8dda3..28dfdc14c7 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java
+++ b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TextEditsBuilder.java
@@ -58,7 +58,7 @@ public TextEditsBuilder(String source, List regions, TokenManager token
 		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/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/Aligner.java b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/Aligner.java
new file mode 100644
index 0000000000..47d33575ca
--- /dev/null
+++ b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/Aligner.java
@@ -0,0 +1,281 @@
+/*******************************************************************************
+ * Copyright (c) 2014, 2018 Mateusz Matela and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Mateusz Matela  - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519
+ *     Lars Vogel  - Contributions for
+ *     						Bug 473178
+ *******************************************************************************/
+package org.eclipse.jdt.internal.formatter.linewrap;
+
+import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_BLOCK;
+import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_LINE;
+import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameEQUAL;
+import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameIdentifier;
+import static java.util.stream.Collectors.toList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.Assignment;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.BodyDeclaration;
+import org.eclipse.jdt.core.dom.ExpressionStatement;
+import org.eclipse.jdt.core.dom.FieldDeclaration;
+import org.eclipse.jdt.core.dom.Statement;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
+import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions;
+import org.eclipse.jdt.internal.formatter.Token;
+import org.eclipse.jdt.internal.formatter.TokenManager;
+import org.eclipse.jdt.internal.formatter.TokenTraverser;
+
+/** Implementation of the "Align items on columns" feature */
+public class Aligner {
+	private class PositionCounter extends TokenTraverser {
+		int stoppingIndex;
+		int maxPosition;
+
+		public PositionCounter() {
+			// nothing to do
+		}
+
+		@Override
+		protected boolean token(Token token, int index) {
+			if (index == this.stoppingIndex)
+				return false;
+			if (getLineBreaksBefore() > 0)
+				this.counter = Aligner.this.tm.getPositionInLine(index);
+			if (token.getAlign() > 0)
+				this.counter = token.getAlign();
+			this.counter += Aligner.this.tm.getLength(token, this.counter);
+			if (isSpaceAfter() && getLineBreaksAfter() == 0)
+				this.counter++;
+			this.maxPosition = Math.max(this.maxPosition, this.counter);
+			return true;
+		}
+
+		public int findMaxPosition(int fromIndex, int toIndex) {
+			this.counter = Aligner.this.tm.getPositionInLine(fromIndex);
+			this.stoppingIndex = toIndex;
+			this.maxPosition = 0;
+			Aligner.this.tm.traverse(fromIndex, this);
+			return this.maxPosition;
+		}
+	}
+
+	@FunctionalInterface
+	private interface AlignIndexFinder {
+		Optional findIndex(N node);
+	}
+
+	private final List> alignGroups = new ArrayList<>();
+
+	private final DefaultCodeFormatterOptions options;
+
+	final TokenManager tm;
+
+	public Aligner(TokenManager tokenManager, DefaultCodeFormatterOptions options) {
+		this.tm = tokenManager;
+		this.options = options;
+	}
+
+	public void handleAlign(List bodyDeclarations) {
+		if (!this.options.align_type_members_on_columns)
+			return;
+		List> fieldGroups = toAlignGroups(bodyDeclarations,
+				n -> optionalCast(n, FieldDeclaration.class));
+		this.alignGroups.addAll(fieldGroups);
+
+		AlignIndexFinder nameFinder = fd -> findName(
+				(VariableDeclarationFragment) fd.fragments().get(0));
+		fieldGroups.forEach(fg -> alignNodes(fg, nameFinder));
+
+		AlignIndexFinder assignFinder = fd -> findAssign(
+				(VariableDeclarationFragment) fd.fragments().get(0));
+		fieldGroups.forEach(fg -> alignNodes(fg, assignFinder));
+	}
+
+	public void handleAlign(Block block) {
+		List statements = block.statements();
+		if (this.options.align_variable_declarations_on_columns)
+			alignDeclarations(statements);
+		if (this.options.align_assignment_statements_on_columns)
+			alignAssignmentStatements(statements);
+	}
+
+	private void alignDeclarations(List statements) {
+		List> variableGroups = toAlignGroups(statements,
+				n -> optionalCast(n, VariableDeclarationStatement.class));
+		this.alignGroups.addAll(variableGroups);
+
+		AlignIndexFinder nameFinder = vd -> findName(
+				(VariableDeclarationFragment) vd.fragments().get(0));
+		variableGroups.forEach(vg -> alignNodes(vg, nameFinder));
+
+		AlignIndexFinder assignFinder = vd -> findAssign(
+				(VariableDeclarationFragment) vd.fragments().get(0));
+		variableGroups.forEach(vg -> alignNodes(vg, assignFinder));
+	}
+
+	private void alignAssignmentStatements(List statements) {
+		List> assignmentGroups = toAlignGroups(statements,
+				n -> optionalCast(n, ExpressionStatement.class)
+						.filter(es -> es.getExpression() instanceof Assignment));
+		this.alignGroups.addAll(assignmentGroups);
+
+		AlignIndexFinder assignFinder = es -> {
+			Assignment a = (Assignment) es.getExpression();
+			int operatorIndex = this.tm.firstIndexBefore(a.getRightHandSide(), -1);
+			while (this.tm.get(operatorIndex).isComment())
+				operatorIndex--;
+			return Optional.of(operatorIndex);
+		};
+		assignmentGroups.forEach(ag -> alignNodes(ag, assignFinder));
+
+		if (this.options.align_with_spaces || this.options.tab_char != DefaultCodeFormatterOptions.TAB) {
+			// align assign operators on their right side (e.g. +=, >>=)
+			for (List group : assignmentGroups) {
+				List assignTokens = group.stream()
+						.map(assignFinder::findIndex)
+						.filter(Optional::isPresent)
+						.map(o -> this.tm.get(o.get()))
+						.collect(toList());
+				int maxWidth = assignTokens.stream().mapToInt(Token::countChars).max().orElse(0);
+				for (Token token : assignTokens)
+					token.setAlign(token.getAlign() + maxWidth - token.countChars());
+			}
+		}
+	}
+
+	private  Optional optionalCast(ASTNode node, Class c) {
+		return Optional.of(node).filter(c::isInstance).map(c::cast);
+	}
+
+	private Optional findName(VariableDeclarationFragment fragment) {
+		int nameIndex = this.tm.firstIndexIn(fragment.getName(), TokenNameIdentifier);
+		return Optional.of(nameIndex);
+	}
+
+	private Optional findAssign(VariableDeclarationFragment fragment) {
+		return Optional.ofNullable(fragment.getInitializer())
+				.map(i -> this.tm.firstIndexBefore(i, TokenNameEQUAL));
+	}
+
+	private  List> toAlignGroups(List nodes,
+			Function> nodeConverter) {
+		List> result = new ArrayList<>();
+		List alignGroup = new ArrayList<>();
+		N previous = null;
+		for (ASTNode node : nodes) {
+			Optional converted = nodeConverter.apply(node);
+			if (converted.isPresent()) {
+				if (isNewGroup(node, previous)) {
+					result.add(alignGroup);
+					alignGroup = new ArrayList<>();
+				}
+				alignGroup.add(converted.get());
+			}
+			previous = converted.orElse(null);
+		}
+		result.add(alignGroup);
+		result.removeIf(l -> l.size() < 2);
+		return result;
+	}
+
+	private boolean isNewGroup(ASTNode node, ASTNode previousNode) {
+		if (previousNode == null)
+			return true;
+		int lineBreaks = 0;
+		int from = this.tm.lastIndexIn(previousNode, -1);
+		int to = this.tm.firstIndexIn(node, -1);
+		Token previousToken = this.tm.get(from);
+		for (int i = from + 1; i <= to; i++) {
+			Token token = this.tm.get(i);
+			lineBreaks += Math.min(this.tm.countLineBreaksBetween(previousToken, token),
+					this.options.number_of_empty_lines_to_preserve + 1);
+			previousToken = token;
+		}
+		return lineBreaks > this.options.align_fields_grouping_blank_lines;
+	}
+
+	private  void alignNodes(List alignGroup, AlignIndexFinder tokenFinder) {
+		int[] tokenIndexes = alignGroup.stream()
+				.map(tokenFinder::findIndex)
+				.filter(Optional::isPresent)
+				.mapToInt(Optional::get).toArray();
+		OptionalInt maxPosition = IntStream.of(tokenIndexes).map(this.tm::getPositionInLine).max();
+		if (maxPosition.isPresent()) {
+			int align = normalizedAlign(maxPosition.getAsInt());
+			for (int tokenIndex : tokenIndexes)
+				this.tm.get(tokenIndex).setAlign(align);
+		}
+	}
+
+	public void alignComments() {
+		boolean alignLineComments = !this.options.comment_preserve_white_space_between_code_and_line_comments;
+		PositionCounter positionCounter = new PositionCounter();
+		// align comments after field declarations
+		for (List alignGroup : this.alignGroups) {
+			int maxCommentAlign = 0;
+			for (ASTNode node : alignGroup) {
+				int firstIndexInLine = findFirstTokenInLine(node);
+				int lastIndex = this.tm.lastIndexIn(node, -1) + 1;
+				maxCommentAlign = Math.max(maxCommentAlign,
+						positionCounter.findMaxPosition(firstIndexInLine, lastIndex));
+			}
+			maxCommentAlign = normalizedAlign(maxCommentAlign);
+
+			for (ASTNode node : alignGroup) {
+				int firstIndexInLine = findFirstTokenInLine(node);
+				int lastIndex = this.tm.lastIndexIn(node, -1);
+				lastIndex = Math.min(lastIndex, this.tm.size() - 2);
+				for (int i = firstIndexInLine; i <= lastIndex; i++) {
+					Token token = this.tm.get(i);
+					Token next = this.tm.get(i + 1);
+					boolean lineBreak = token.getLineBreaksAfter() > 0 || next.getLineBreaksBefore() > 0;
+					if (lineBreak) {
+						if (token.tokenType == TokenNameCOMMENT_BLOCK) {
+							token.setAlign(maxCommentAlign);
+						} else if (alignLineComments) {
+							this.tm.addNLSAlignIndex(i, maxCommentAlign);
+						}
+					} else if (next.tokenType == TokenNameCOMMENT_LINE && alignLineComments
+							|| (next.tokenType == TokenNameCOMMENT_BLOCK && i == lastIndex)) {
+						next.setAlign(maxCommentAlign);
+					}
+				}
+			}
+		}
+	}
+
+	private int findFirstTokenInLine(ASTNode node) {
+		if (node instanceof FieldDeclaration) {
+			int typeIndex = this.tm.firstIndexIn(((FieldDeclaration) node).getType(), -1);
+			return this.tm.findFirstTokenInLine(typeIndex);
+		}
+		if (node instanceof VariableDeclarationStatement) {
+			int typeIndex = this.tm.firstIndexIn(((VariableDeclarationStatement) node).getType(), -1);
+			return this.tm.findFirstTokenInLine(typeIndex);
+		}
+		if (node instanceof ExpressionStatement) {
+			return this.tm.firstIndexIn(node, -1);
+		}
+		throw new IllegalArgumentException(node.getClass().getName());
+	}
+
+	private int normalizedAlign(int desiredAlign) {
+		if (this.options.align_with_spaces)
+			return desiredAlign;
+		return this.tm.toIndent(desiredAlign, false);
+	}
+}
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/FieldAligner.java b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/FieldAligner.java
deleted file mode 100644
index 9af0fab33c..0000000000
--- a/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/FieldAligner.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014, 2016 Mateusz Matela and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *     Mateusz Matela  - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519
- *     Lars Vogel  - Contributions for
- *     						Bug 473178
- *******************************************************************************/
-package org.eclipse.jdt.internal.formatter.linewrap;
-
-import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_BLOCK;
-import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_LINE;
-import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameEQUAL;
-import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameIdentifier;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jdt.core.dom.BodyDeclaration;
-import org.eclipse.jdt.core.dom.FieldDeclaration;
-import org.eclipse.jdt.core.dom.SimpleName;
-import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
-import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions;
-import org.eclipse.jdt.internal.formatter.Token;
-import org.eclipse.jdt.internal.formatter.TokenManager;
-import org.eclipse.jdt.internal.formatter.TokenTraverser;
-
-/** Implementation of the "Align fields on columns" feature */
-public class FieldAligner {
-	private class PositionCounter extends TokenTraverser {
-		int stoppingIndex;
-		int maxPosition;
-
-		public PositionCounter() {
-			// nothing to do
-		}
-
-		@Override
-		protected boolean token(Token token, int index) {
-			if (index == this.stoppingIndex)
-				return false;
-			if (getLineBreaksBefore() > 0)
-				this.counter = FieldAligner.this.tm.getPositionInLine(index);
-			if (token.getAlign() > 0)
-				this.counter = token.getAlign();
-			this.counter += FieldAligner.this.tm.getLength(token, this.counter);
-			if (isSpaceAfter() && getLineBreaksAfter() == 0)
-				this.counter++;
-			this.maxPosition = Math.max(this.maxPosition, this.counter);
-			return true;
-		}
-
-		public int findMaxPosition(int fromIndex, int toIndex) {
-			this.counter = FieldAligner.this.tm.getPositionInLine(fromIndex);
-			this.stoppingIndex = toIndex;
-			this.maxPosition = 0;
-			FieldAligner.this.tm.traverse(fromIndex, this);
-			return this.maxPosition;
-		}
-	}
-
-	private final List> fieldAlignGroups = new ArrayList<>();
-
-	private final DefaultCodeFormatterOptions options;
-
-	final TokenManager tm;
-
-	public FieldAligner(TokenManager tokenManager, DefaultCodeFormatterOptions options) {
-		this.tm = tokenManager;
-		this.options = options;
-	}
-
-	public void handleAlign(List bodyDeclarations) {
-		if (!this.options.align_type_members_on_columns)
-			return;
-		ArrayList alignGroup = new ArrayList<>();
-		BodyDeclaration previous = null;
-		for (BodyDeclaration declaration : bodyDeclarations) {
-			if (declaration instanceof FieldDeclaration) {
-				if (isNewGroup(declaration, previous)) {
-					alignFields(alignGroup);
-					alignGroup = new ArrayList<>();
-				}
-				alignGroup.add((FieldDeclaration) declaration);
-			}
-			previous = declaration;
-		}
-		alignFields(alignGroup);
-	}
-
-	private boolean isNewGroup(BodyDeclaration declaration, BodyDeclaration previousDeclaration) {
-		if (!(previousDeclaration instanceof FieldDeclaration))
-			return true;
-		int lineBreaks = 0;
-		int from = this.tm.lastIndexIn(previousDeclaration, -1);
-		int to = this.tm.firstIndexIn(declaration, -1);
-		Token previous = this.tm.get(from);
-		for (int i = from + 1; i <= to; i++) {
-			Token token = this.tm.get(i);
-			lineBreaks += Math.min(this.tm.countLineBreaksBetween(previous, token),
-					this.options.number_of_empty_lines_to_preserve + 1);
-			previous = token;
-		}
-		return lineBreaks > this.options.align_fields_grouping_blank_lines;
-	}
-
-	private void alignFields(ArrayList alignGroup) {
-		if (alignGroup.size() < 2)
-			return;
-		this.fieldAlignGroups.add(alignGroup);
-
-		int maxNameAlign = 0;
-		for (FieldDeclaration declaration : alignGroup) {
-			List fragments = declaration.fragments();
-			SimpleName fieldName = fragments.get(0).getName();
-			int nameIndex = this.tm.firstIndexIn(fieldName, TokenNameIdentifier);
-			int positionInLine = this.tm.getPositionInLine(nameIndex);
-			maxNameAlign = Math.max(maxNameAlign, positionInLine);
-		}
-		maxNameAlign = this.tm.toIndent(maxNameAlign, false);
-
-		int maxAssignAlign = 0;
-		for (FieldDeclaration declaration : alignGroup) {
-			List fragments = declaration.fragments();
-			VariableDeclarationFragment fragment = fragments.get(0);
-			int nameIndex = this.tm.firstIndexIn(fragment.getName(), TokenNameIdentifier);
-			Token nameToken = this.tm.get(nameIndex);
-
-			nameToken.setAlign(maxNameAlign);
-
-			if (fragment.getInitializer() != null) {
-				int equalIndex = this.tm.firstIndexAfter(fragment.getName(), TokenNameEQUAL);
-				int positionInLine = this.tm.getPositionInLine(equalIndex);
-				maxAssignAlign = Math.max(maxAssignAlign, positionInLine);
-			}
-		}
-		maxAssignAlign = this.tm.toIndent(maxAssignAlign, false);
-
-		for (FieldDeclaration declaration : alignGroup) {
-			List fragments = declaration.fragments();
-			VariableDeclarationFragment fragment = fragments.get(0);
-			if (fragment.getInitializer() != null) {
-				int assingIndex = this.tm.firstIndexAfter(fragment.getName(), TokenNameEQUAL);
-				Token assignToken = this.tm.get(assingIndex);
-				assignToken.setAlign(maxAssignAlign);
-			}
-		}
-	}
-
-	public void alignComments() {
-		if (this.fieldAlignGroups.isEmpty())
-			return;
-		boolean alignLineComments = !this.options.comment_preserve_white_space_between_code_and_line_comments;
-		PositionCounter positionCounter = new PositionCounter();
-		// align comments after field declarations
-		for (List alignGroup : this.fieldAlignGroups) {
-			int maxCommentAlign = 0;
-			for (FieldDeclaration declaration : alignGroup) {
-				int typeIndex = this.tm.firstIndexIn(declaration.getType(), -1);
-				int firstIndexInLine = this.tm.findFirstTokenInLine(typeIndex);
-				int lastIndex = this.tm.lastIndexIn(declaration, -1) + 1;
-				maxCommentAlign = Math.max(maxCommentAlign,
-						positionCounter.findMaxPosition(firstIndexInLine, lastIndex));
-			}
-			maxCommentAlign = this.tm.toIndent(maxCommentAlign, false);
-
-			for (FieldDeclaration declaration : alignGroup) {
-				int typeIndex = this.tm.firstIndexIn(declaration.getType(), -1);
-				int firstIndexInLine = this.tm.findFirstTokenInLine(typeIndex);
-				int lastIndex = this.tm.lastIndexIn(declaration, -1);
-				lastIndex = Math.min(lastIndex, this.tm.size() - 2);
-				for (int i = firstIndexInLine; i <= lastIndex; i++) {
-					Token token = this.tm.get(i);
-					Token next = this.tm.get(i + 1);
-					boolean lineBreak = token.getLineBreaksAfter() > 0 || next.getLineBreaksBefore() > 0;
-					if (lineBreak) {
-						if (token.tokenType == TokenNameCOMMENT_BLOCK) {
-							token.setAlign(maxCommentAlign);
-						} else if (alignLineComments) {
-							this.tm.addNLSAlignIndex(i, maxCommentAlign);
-						}
-					} else if (next.tokenType == TokenNameCOMMENT_LINE && alignLineComments
-							|| (next.tokenType == TokenNameCOMMENT_BLOCK && i == lastIndex)) {
-						next.setAlign(maxCommentAlign);
-					}
-				}
-			}
-		}
-	}
-}
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
index 9987d6a54b..1b684337fd 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
+++ b/jdt-patch/e49/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
@@ -175,7 +175,7 @@ public int getIdentifierIndex(TokenManager tm) {
 	final DefaultCodeFormatterOptions options;
 	final int kind;
 
-	final FieldAligner fieldAligner;
+	final Aligner aligner;
 
 	int importsStart = -1, importsEnd = -1;
 
@@ -197,7 +197,7 @@ public WrapPreparator(TokenManager tokenManager, DefaultCodeFormatterOptions opt
 		this.options = options;
 		this.kind = kind;
 
-		this.fieldAligner = new FieldAligner(this.tm, this.options);
+		this.aligner = new Aligner(this.tm, this.options);
 	}
 
 	@Override
@@ -258,20 +258,20 @@ public boolean visit(TypeDeclaration node) {
 		prepareElementsList(node.typeParameters(), TokenNameCOMMA, TokenNameLESS);
 		handleWrap(this.options.alignment_for_type_parameters);
 
-		this.fieldAligner.handleAlign(node.bodyDeclarations());
+		this.aligner.handleAlign(node.bodyDeclarations());
 
 		return true;
 	}
 
 	@Override
 	public boolean visit(AnnotationTypeDeclaration node) {
-		this.fieldAligner.handleAlign(node.bodyDeclarations());
+		this.aligner.handleAlign(node.bodyDeclarations());
 		return true;
 	}
 
 	@Override
 	public boolean visit(AnonymousClassDeclaration node) {
-		this.fieldAligner.handleAlign(node.bodyDeclarations());
+		this.aligner.handleAlign(node.bodyDeclarations());
 		return true;
 	}
 
@@ -367,7 +367,7 @@ public boolean visit(EnumDeclaration node) {
 			handleWrap(this.options.alignment_for_superinterfaces_in_enum_declaration, PREFERRED);
 		}
 
-		this.fieldAligner.handleAlign(node.bodyDeclarations());
+		this.aligner.handleAlign(node.bodyDeclarations());
 
 		return true;
 	}
@@ -382,6 +382,12 @@ public boolean visit(EnumConstantDeclaration node) {
 		return true;
 	}
 
+	@Override
+	public boolean visit(Block node) {
+		this.aligner.handleAlign(node);
+		return true;
+	}
+
 	@Override
 	public boolean visit(MethodInvocation node) {
 		handleArguments(node.arguments(), this.options.alignment_for_arguments_in_method_invocation);
@@ -1075,7 +1081,7 @@ public void finishUp(ASTNode astRoot, List regions) {
 		preserveExistingLineBreaks();
 		applyBreaksOutsideRegions(regions);
 		new WrapExecutor(this.tm, this.options).executeWraps();
-		this.fieldAligner.alignComments();
+		this.aligner.alignComments();
 		wrapComments();
 		fixEnumConstantIndents(astRoot);
 	}
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java b/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java
index 561c6084a6..a38d4a4e29 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java
+++ b/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java
@@ -14,8 +14,9 @@
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.jar.Manifest;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
@@ -68,7 +69,6 @@ public class JarPackageFragmentRoot extends PackageFragmentRoot {
 	boolean knownToBeModuleLess;
 
 	private boolean multiVersion;
-	public String versionPath;
 
 	/**
 	 * Constructs a package fragment root which is the root of the Java package directory hierarchy
@@ -96,8 +96,8 @@ protected JarPackageFragmentRoot(IResource resource, JavaProject project) {
 	@Override
 	protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws JavaModelException {
 		final HashtableOfArrayToObject rawPackageInfo = new HashtableOfArrayToObject();
-		final Set overridden = new HashSet<>();
-		IJavaElement[] children;
+		final Map overridden = new HashMap<>();
+		IJavaElement[] children = NO_ELEMENTS;
 		try {
 			// always create the default package
 			rawPackageInfo.put(CharOperation.NO_STRINGS, new ArrayList[] { EMPTY_LIST, EMPTY_LIST });
@@ -139,19 +139,38 @@ protected boolean computeChildren(OpenableElementInfo info, IResource underlying
 				ZipFile jar = null;
 				try {
 					jar = getJar();
-					String version = "META-INF/versions/" + projectCompliance + "/";  //$NON-NLS-1$//$NON-NLS-2$
-					int versionPathLength = version.length();
+					String version = "META-INF/versions/";  //$NON-NLS-1$
+					List versions = new ArrayList<>();
 					if (projectLevel >= ClassFileConstants.JDK9 && jar.getEntry(version) != null) {
+						int earliestJavaVersion = ClassFileConstants.MAJOR_VERSION_9;
+						long latestJDK = CompilerOptions.releaseToJDKLevel(projectCompliance);
+						int latestJavaVer = (int) (latestJDK >> 16);
+
+						for(int i = latestJavaVer; i >= earliestJavaVersion; i--) {
+							String s = "" + + (i - 44); //$NON-NLS-1$
+							String versionPath = version + s;
+							if (jar.getEntry(versionPath) != null) {
+								versions.add(s);
+							}
+						}
+					}
+					
+					String[] supportedVersions = versions.toArray(new String[versions.size()]);
+					if (supportedVersions.length > 0) {
 						this.multiVersion = true;
-						this.versionPath = version;
 					}
-					for (Enumeration e= jar.entries(); e.hasMoreElements();) {
-						ZipEntry member= (ZipEntry) e.nextElement();
+					int length = version.length();
+					for (Enumeration e= jar.entries(); e.hasMoreElements();) {
+						ZipEntry member= e.nextElement();
 						String name = member.getName();
-						if (this.multiVersion && name.length() > versionPathLength && name.startsWith(version)) {
-							name = name.substring(version.length());
-							if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) {
-								overridden.add(name);
+						if (this.multiVersion && name.length() > (length + 2) && name.startsWith(version)) {
+							int end = name.indexOf('/', length);
+							if (end >= name.length()) continue;
+							String versionPath = name.substring(0, end);
+							String ver = name.substring(length, end);
+							if(versions.contains(ver) && org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) {
+								name = name.substring(end + 1);
+								overridden.put(name, versionPath);
 							}
 						}
 						initRawPackageInfo(rawPackageInfo, name, member.isDirectory(), CompilerOptions.versionFromJdkLevel(classLevel));
@@ -180,7 +199,6 @@ protected boolean computeChildren(OpenableElementInfo info, IResource underlying
 				throw new JavaModelException(e);
 			}
 		}
-
 		info.setChildren(children);
 		((JarPackageFragmentRootInfo) info).rawPackageInfo = rawPackageInfo;
 		((JarPackageFragmentRootInfo) info).overriddenClasses = overridden;
@@ -285,9 +303,8 @@ public String getClassFilePath(String classname) {
 			JarPackageFragmentRootInfo elementInfo;
 			try {
 				elementInfo = (JarPackageFragmentRootInfo) getElementInfo();
-				if (elementInfo.overriddenClasses.contains(classname)) {
-					return this.versionPath == null ? classname : this.versionPath + classname;
-				}
+				String versionPath = elementInfo.overriddenClasses.get(classname);
+				return versionPath == null ? classname : versionPath + '/' + classname;
 			} catch (JavaModelException e) {
 				// move on
 			}
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRootInfo.java b/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRootInfo.java
index 113c5bfb37..1aa3bdcc1d 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRootInfo.java
+++ b/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRootInfo.java
@@ -10,7 +10,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core;
 
-import java.util.Set;
+import java.util.Map;
 
 import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject;
 
@@ -20,6 +20,6 @@
 class JarPackageFragmentRootInfo extends PackageFragmentRootInfo {
 	// a map from package name (String[]) to a size-2 array of Array, the first element being the .class file names, and the second element being the non-Java resource names
 	HashtableOfArrayToObject rawPackageInfo;
-	Set overriddenClasses;
+	Map overriddenClasses;
 	
 }
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java b/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java
index a0b66c437d..314926d152 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java
+++ b/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java
@@ -93,6 +93,8 @@ protected String readJarContent(final SimpleSet packageSet) {
 	String modInfo = null;
 	for (Enumeration e = this.zipFile.entries(); e.hasMoreElements(); ) {
 		String fileName = ((ZipEntry) e.nextElement()).getName();
+		if (fileName.startsWith("META-INF/")) //$NON-NLS-1$
+			continue;
 		if (modInfo == null) {
 			int folderEnd = fileName.lastIndexOf('/');
 			folderEnd += 1;
@@ -111,11 +113,9 @@ IModule initializeModule() {
 	try {
 		file = new ZipFile(this.zipFilename);
 		String releasePath = "META-INF/versions/" + this.compliance + '/' + IModule.MODULE_INFO_CLASS; //$NON-NLS-1$
-		System.out.println("Reading for module from: " + this.zipFilename); //$NON-NLS-1$
 		ClassFileReader classfile = null;
 		try {
 			classfile = ClassFileReader.read(file, releasePath);
-			System.out.println("Read classfile : " + classfile); //$NON-NLS-1$
 		} catch (Exception e) {
 			e.printStackTrace();
 			// move on to the default
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiReleaseJar.java b/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiReleaseJar.java
index d5954151de..9b03ee6e02 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiReleaseJar.java
+++ b/jdt-patch/e49/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiReleaseJar.java
@@ -2,7 +2,6 @@
 
 import java.io.IOException;
 import java.net.URI;
-import java.nio.file.DirectoryStream;
 import java.nio.file.FileSystemNotFoundException;
 import java.nio.file.FileSystems;
 import java.nio.file.FileVisitResult;
@@ -12,12 +11,15 @@
 import java.nio.file.Paths;
 import java.nio.file.ProviderNotFoundException;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.function.Predicate;
 import java.util.zip.ZipFile;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.runtime.IPath;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
 import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
@@ -25,6 +27,7 @@
 import org.eclipse.jdt.internal.compiler.env.IBinaryType;
 import org.eclipse.jdt.internal.compiler.env.IModule;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus;
 import org.eclipse.jdt.internal.compiler.util.SimpleSet;
 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
@@ -32,36 +35,84 @@
 public class ClasspathMultiReleaseJar extends ClasspathJar {
 	private java.nio.file.FileSystem fs = null;
 	Path releasePath = null;
+	Path rootPath = null;
+	Path[] supportedVersions;
 
-	ClasspathMultiReleaseJar(IFile resource, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath, String compliance) {
+	ClasspathMultiReleaseJar(IFile resource, AccessRuleSet accessRuleSet, IPath externalAnnotationPath,
+			boolean isOnModulePath, String compliance) {
 		super(resource, accessRuleSet, externalAnnotationPath, isOnModulePath);
 		this.compliance = compliance;
 		initializeVersions();
 	}
 
-	ClasspathMultiReleaseJar(String zipFilename, long lastModified, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath, String compliance) {
+	ClasspathMultiReleaseJar(String zipFilename, long lastModified, AccessRuleSet accessRuleSet,
+			IPath externalAnnotationPath, boolean isOnModulePath, String compliance) {
 		super(zipFilename, lastModified, accessRuleSet, externalAnnotationPath, isOnModulePath);
 		this.compliance = compliance;
 		initializeVersions();
 	}
 
-	public ClasspathMultiReleaseJar(ZipFile zipFile, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath, String compliance) {
+	public ClasspathMultiReleaseJar(ZipFile zipFile, AccessRuleSet accessRuleSet, IPath externalAnnotationPath,
+			boolean isOnModulePath, String compliance) {
 		this(zipFile.getName(), accessRuleSet, externalAnnotationPath, isOnModulePath, compliance);
 		this.zipFile = zipFile;
 		this.closeZipFileAtEnd = true;
 	}
 
-	public ClasspathMultiReleaseJar(String fileName, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath, String compliance) {
+	public ClasspathMultiReleaseJar(String fileName, AccessRuleSet accessRuleSet, IPath externalAnnotationPath,
+			boolean isOnModulePath, String compliance) {
 		this(fileName, 0, accessRuleSet, externalAnnotationPath, isOnModulePath, compliance);
 		if (externalAnnotationPath != null)
 			this.externalAnnotationPath = externalAnnotationPath.toString();
 	}
+
+	@Override
+	IModule initializeModule() {
+		IModule mod = null;
+		ZipFile file = null;
+		try {
+			file = new ZipFile(this.zipFilename);
+			ClassFileReader classfile = null;
+			try {
+				for (Path path : this.supportedVersions) {
+					classfile = ClassFileReader.read(file, path.toString() + '/' + IModule.MODULE_INFO_CLASS);
+					if (classfile != null)
+						break;
+				}
+
+			} catch (Exception e) {
+				e.printStackTrace();
+				// move on to the default
+			}
+			if (classfile == null) {
+				classfile = ClassFileReader.read(file, IModule.MODULE_INFO_CLASS); // FIXME: use jar cache
+			}
+			if (classfile != null) {
+				mod = classfile.getModuleDeclaration();
+			}
+		} catch (ClassFormatException | IOException e) {
+			// do nothing
+		} finally {
+			try {
+				if (file != null)
+					file.close();
+			} catch (IOException e) {
+				// do nothing
+			}
+		}
+		return mod;
+	}
+
 	private void initializeVersions() {
 		Path filePath = Paths.get(this.zipFilename);
 		if (Files.exists(filePath)) {
-			URI uri = URI.create("jar:" + filePath.toUri());  //$NON-NLS-1$
+			URI uri = URI.create("jar:" + filePath.toUri()); //$NON-NLS-1$
 			try {
-				this.fs = FileSystems.getFileSystem(uri);
+				try {
+					this.fs = FileSystems.getFileSystem(uri);
+				} catch (FileSystemNotFoundException e) {
+					// move on
+				}
 				if (this.fs == null) {
 					HashMap env = new HashMap<>();
 					this.fs = FileSystems.newFileSystem(uri, env);
@@ -71,75 +122,88 @@ private void initializeVersions() {
 			} catch (IOException e) {
 				// move on
 			}
-			if (this.fs == null) {
-				this.releasePath = null;
-			} else {
-				this.releasePath = this.fs.getPath("/", "META-INF", "versions", this.compliance); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-				if (!Files.exists(this.releasePath)) {
-					this.releasePath = null;
-					try {
-						this.fs.close();
-					} catch (IOException e) {
-						// ignore
-					}
+			if (this.fs == null)
+				return;
+			this.rootPath = this.fs.getPath("/"); //$NON-NLS-1$
+			int earliestJavaVersion = ClassFileConstants.MAJOR_VERSION_9;
+			long latestJDK = CompilerOptions.releaseToJDKLevel(this.compliance);
+			int latestJavaVer = (int) (latestJDK >> 16);
+			List versions = new ArrayList<>();
+			for (int i = latestJavaVer; i >= earliestJavaVersion; i--) {
+				Path path = this.fs.getPath("/", "META-INF", "versions", "" + (i - 44)); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+				if (Files.exists(path)) {
+					versions.add(this.rootPath.relativize(path));
+				}
+			}
+			this.supportedVersions = versions.toArray(new Path[versions.size()]);
+			if (this.supportedVersions.length <= 0) {
+				try {
+					this.fs.close();
+				} catch (IOException e) {
+					// ignore
 				}
 			}
 		}
 	}
+
 	@Override
 	protected String readJarContent(final SimpleSet packageSet) {
 		String[] modInfo = new String[1];
 		modInfo[0] = super.readJarContent(packageSet);
 		try {
-			if (this.releasePath != null && Files.exists(this.releasePath)) {
-				// go through the packages
-				try (DirectoryStream stream = Files.newDirectoryStream(this.releasePath)) {
-					for (final java.nio.file.Path subdir: stream) {
-						Files.walkFileTree(subdir, new FileVisitor() {
-							@Override
-							public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs)
-									throws IOException {
-								return FileVisitResult.CONTINUE;
-							}
-							@Override
-							public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs)
-									throws IOException {
-								Path p = ClasspathMultiReleaseJar.this.releasePath.relativize(file);
-								addToPackageSet(packageSet, p.toString(), false);
-								if (modInfo[0] == null) {
-									if (p.getFileName().toString().equalsIgnoreCase(IModule.MODULE_INFO_CLASS)) {
-										 modInfo[0] = ClasspathMultiReleaseJar.this.releasePath.relativize(file).toString();
-									}
-								}
-								return FileVisitResult.CONTINUE;
-							}
+			for (Path path : this.supportedVersions) {
+				Path relativePath = this.rootPath.resolve(path);
+				Files.walkFileTree(path, new FileVisitor() {
+					@Override
+					public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs)
+							throws IOException {
+						return FileVisitResult.CONTINUE;
+					}
 
-							@Override
-							public FileVisitResult visitFileFailed(java.nio.file.Path file, IOException exc) throws IOException {
-								return FileVisitResult.CONTINUE;
+					@Override
+					public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs)
+							throws IOException {
+						Path p = relativePath.relativize(file);
+						addToPackageSet(packageSet, p.toString(), false);
+						if (modInfo[0] == null) {
+							if (p.getFileName().toString().equalsIgnoreCase(IModule.MODULE_INFO_CLASS)) {
+								modInfo[0] = relativePath.relativize(file).toString();
 							}
+						}
+						return FileVisitResult.CONTINUE;
+					}
 
-							@Override
-							public FileVisitResult postVisitDirectory(java.nio.file.Path dir, IOException exc)
-									throws IOException {
-								return FileVisitResult.CONTINUE;
-							}
-						});
+					@Override
+					public FileVisitResult visitFileFailed(java.nio.file.Path file, IOException exc)
+							throws IOException {
+						return FileVisitResult.CONTINUE;
 					}
-				}
+
+					@Override
+					public FileVisitResult postVisitDirectory(java.nio.file.Path dir, IOException exc)
+							throws IOException {
+						return FileVisitResult.CONTINUE;
+					}
+				});
 			}
 		} catch (Exception e) {
 			// move on;
 		}
 		return modInfo[0];
 	}
+
 	@Override
-	public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly, Predicate moduleNameFilter) {
-		if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case
-		if (this.releasePath != null) {
+	public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPackageName, String moduleName,
+			String qualifiedBinaryFileName, boolean asBinaryOnly, Predicate moduleNameFilter) {
+		if (!isPackage(qualifiedPackageName, moduleName))
+			return null; // most common case
+		for (Path path : this.supportedVersions) {
+			Path relativePath = this.rootPath.resolve(path);
 			try {
-				Path path = this.releasePath.resolve(qualifiedPackageName).resolve(binaryFileName);
-				byte[] content = Files.readAllBytes(path);
+				Path p = relativePath.resolve(qualifiedPackageName).resolve(binaryFileName);
+				if (!Files.exists(p))
+					continue;
+				byte[] content = Files.readAllBytes(p);
 				IBinaryType reader = null;
 				if (content != null) {
 					reader = new ClassFileReader(content, qualifiedBinaryFileName.toCharArray());
@@ -152,8 +216,9 @@ public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPa
 							classReader.moduleName = modName;
 						else
 							modName = classReader.moduleName;
-						}
-					String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length);
+					}
+					String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0,
+							qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length);
 					if (this.externalAnnotationPath != null) {
 						try {
 							if (this.annotationZipFile == null) {
@@ -173,14 +238,15 @@ public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPa
 					}
 					if (this.accessRuleSet == null)
 						return new NameEnvironmentAnswer(reader, null, modName);
-					return new NameEnvironmentAnswer(reader, 
-							this.accessRuleSet.getViolatedRestriction(fileNameWithoutExtension.toCharArray()), 
-							modName);
+					return new NameEnvironmentAnswer(reader,
+							this.accessRuleSet.getViolatedRestriction(fileNameWithoutExtension.toCharArray()), modName);
 				}
 			} catch (IOException | ClassFormatException e) {
+				e.printStackTrace();
 				// treat as if class file is missing
 			}
 		}
-		return super.findClass(binaryFileName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, asBinaryOnly, moduleNameFilter);
+		return super.findClass(binaryFileName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, asBinaryOnly,
+				moduleNameFilter);
 	}
 }
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/readme.txt b/jdt-patch/e49/org.eclipse.jdt.core/readme.txt
index b3136f76ce..6549eb434d 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/readme.txt
+++ b/jdt-patch/e49/org.eclipse.jdt.core/readme.txt
@@ -1,2 +1,3 @@
 2018-08-03: 3b0b35b (2018-09 M2)
 2018-08-24: 88a2114 (2018-09 M3)
+2018-09-02: 987e180 (2018-09 RC1)
diff --git a/jdt-patch/e49/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java b/jdt-patch/e49/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java
index d5e26b9bda..c783aab41d 100644
--- a/jdt-patch/e49/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java
+++ b/jdt-patch/e49/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java
@@ -74,7 +74,8 @@ private static BinaryTypeDescriptor createDescriptor(PackageFragment pkg, ClassF
 		String overridePath = root.getClassFilePath(entryName);
 		if (overridePath != entryName) {
 			entryName = overridePath;
-			name = ((JarPackageFragmentRoot) root).versionPath + name;
+			String versionPath = overridePath.substring(0, overridePath.indexOf(entryName));
+			name = versionPath + name;
 		}
 		char[] fieldDescriptor = CharArrayUtils.concat(new char[] { 'L' },
 				name.toCharArray(), new char[] { ';' });