blob: 3e0863ea0703c44a46a73b6bbf2c0901d0cd5204 [file] [log] [blame]
/*******************************************************************************
* 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 <mateusz.matela@gmail.com> - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519
* Lars Vogel <Lars.Vogel@vogella.com> - 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<List<FieldDeclaration>> 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<FieldDeclaration> bodyDeclarations) {
if (!this.options.align_type_members_on_columns)
return;
ArrayList<FieldDeclaration> 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<FieldDeclaration> alignGroup) {
if (alignGroup.size() < 2)
return;
this.fieldAlignGroups.add(alignGroup);
int maxNameAlign = 0;
for (FieldDeclaration declaration : alignGroup) {
List<VariableDeclarationFragment> 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 = normalizedAlign(maxNameAlign);
int maxAssignAlign = 0;
for (FieldDeclaration declaration : alignGroup) {
List<VariableDeclarationFragment> 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 = normalizedAlign(maxAssignAlign);
for (FieldDeclaration declaration : alignGroup) {
List<VariableDeclarationFragment> 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<FieldDeclaration> 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 = normalizedAlign(maxCommentAlign);
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);
}
}
}
}
}
private int normalizedAlign(int desiredAlign) {
if (this.options.align_with_spaces)
return desiredAlign;
return this.tm.toIndent(desiredAlign, false);
}
}