blob: b2b7144267e6a8e0f52e45ea0191e1182749b91a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2015 Mateusz Matela 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:
* 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
* Till Brychcy - Java Code Formatter breaks code if single line comments contain unicode escape - https://bugs.eclipse.org/471090
*******************************************************************************/
package org.eclipse.jdt.internal.formatter;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_BLOCK;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_JAVADOC;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_LINE;
import java.util.List;
import org.eclipse.jdt.internal.compiler.parser.Scanner;
import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
/**
* Stores a token's type, position and all its properties like surrounding whitespace, wrapping behavior and so on.
*/
public class Token {
public static enum WrapMode {
/**
* Wrap mode for the "Do not wrap" policy. Tokens still should be indented as if wrapped when a preceding line
* break cannot be removed due to a line comment or formatting region restriction.
*/
DISABLED,
/** Wrap mode for the "Wrap where necessary" policies. */
WHERE_NECESSARY,
/** Wrap mode for the "Wrap all elements" policies. */
TOP_PRIORITY,
/** Wrap mode for tokens that must be wrapped due to "Force wrap" setting. */
FORCE,
/**
* Wrap mode used for lines in anonymous class and lambda body. Means that tokens that are already in new line
* before wrapping, but their indentation should be adjusted in similar way to wrapping.
*/
BLOCK_INDENT
}
public static class WrapPolicy {
/** Policy used to mark tokens that should never be wrapped */
public final static WrapPolicy DISABLE_WRAP = new WrapPolicy(WrapMode.DISABLED, 0, 0);
/**
* Policy used for internal structure of multiline comments to mark tokens that can be wrapped only in lines
* that have no other tokens to wrap.
*/
public final static WrapPolicy SUBSTITUTE_ONLY = new WrapPolicy(WrapMode.DISABLED, 0, 0);
/** Policy used to mark comments on first column that should not be indented. */
public final static WrapPolicy FORCE_FIRST_COLUMN = new WrapPolicy(WrapMode.DISABLED, 0, 0);
public final WrapMode wrapMode;
public final int wrapParentIndex;
public final int groupEndIndex;
public final int extraIndent;
public final int structureDepth;
public final float penaltyMultiplier;
public final boolean isFirstInGroup;
public final boolean indentOnColumn;
public WrapPolicy(WrapMode wrapMode, int wrapParentIndex, int groupEndIndex, int extraIndent,
int structureDepth, float penaltyMultiplier, boolean isFirstInGroup, boolean indentOnColumn) {
assert wrapMode != null && (wrapParentIndex < groupEndIndex || groupEndIndex == -1);
this.wrapMode = wrapMode;
this.wrapParentIndex = wrapParentIndex;
this.groupEndIndex = groupEndIndex;
this.extraIndent = extraIndent;
this.structureDepth = structureDepth;
this.penaltyMultiplier = penaltyMultiplier;
this.isFirstInGroup = isFirstInGroup;
this.indentOnColumn = indentOnColumn;
}
public WrapPolicy(WrapMode wrapMode, int wrapParentIndex, int extraIndent) {
this(wrapMode, wrapParentIndex, -1, extraIndent, 0, 1, false, false);
}
}
/** Position in source of the first character. */
public final int originalStart;
/** Position in source of the last character (this position is included in the token). */
public final int originalEnd;
/** Type of this token. See {@link TerminalTokens} for constants definition. */
public final int tokenType;
private boolean spaceBefore, spaceAfter;
private int lineBreaksBefore, lineBreaksAfter;
private boolean preserveLineBreaksBefore = true, preserveLineBreaksAfter = true;
private boolean wrapped;
private int indent;
private int emptyLineIndentAdjustment;
private int align;
private boolean toEscape;
private boolean nextLineOnWrap;
private WrapPolicy wrapPolicy;
private Token nlsTagToken;
private List<Token> internalStructure;
public Token(int sourceStart, int sourceEnd, int tokenType) {
assert sourceStart <= sourceEnd;
this.originalStart = sourceStart;
this.originalEnd = sourceEnd;
this.tokenType = tokenType;
}
public Token(Token tokenToCopy) {
this(tokenToCopy, tokenToCopy.originalStart, tokenToCopy.originalEnd, tokenToCopy.tokenType);
}
public Token(Token tokenToCopy, int newOriginalStart, int newOriginalEnd, int newTokenType) {
this.originalStart = newOriginalStart;
this.originalEnd = newOriginalEnd;
this.tokenType = newTokenType;
this.spaceBefore = tokenToCopy.spaceBefore;
this.spaceAfter = tokenToCopy.spaceAfter;
this.lineBreaksBefore = tokenToCopy.lineBreaksBefore;
this.lineBreaksAfter = tokenToCopy.lineBreaksAfter;
this.preserveLineBreaksBefore = tokenToCopy.preserveLineBreaksBefore;
this.preserveLineBreaksAfter = tokenToCopy.preserveLineBreaksAfter;
this.indent = tokenToCopy.indent;
this.nextLineOnWrap = tokenToCopy.nextLineOnWrap;
this.wrapPolicy = tokenToCopy.wrapPolicy;
this.nlsTagToken = tokenToCopy.nlsTagToken;
this.internalStructure = tokenToCopy.internalStructure;
}
public static Token fromCurrent(Scanner scanner, int currentToken) {
int start = scanner.getCurrentTokenStartPosition();
int end = scanner.getCurrentTokenEndPosition();
if (currentToken == TokenNameCOMMENT_LINE) {
// don't include line separator
while(end >= start) {
char c = scanner.source[end];
if (c != '\r' && c != '\n')
break;
end--;
}
}
Token token = new Token(start, end, currentToken);
return token;
}
/** Adds space before this token */
public void spaceBefore() {
this.spaceBefore = true;
}
/** Removes space before this token */
public void clearSpaceBefore() {
this.spaceBefore = false;
}
public boolean isSpaceBefore() {
return this.spaceBefore;
}
/** Adds space after this token */
public void spaceAfter() {
this.spaceAfter = true;
}
/** Removes space after this token */
public void clearSpaceAfter() {
this.spaceAfter = false;
}
public boolean isSpaceAfter() {
return this.spaceAfter;
}
public void breakBefore() {
putLineBreaksBefore(1);
}
public void putLineBreaksBefore(int lineBreaks) {
this.lineBreaksBefore = Math.max(this.lineBreaksBefore, lineBreaks);
}
public int getLineBreaksBefore() {
return this.wrapped ? 1 : this.lineBreaksBefore;
}
/** Can be used to temporarily force preceding line break without losing the original number of line breaks. */
public void setWrapped(boolean wrapped) {
this.wrapped = wrapped;
}
public void clearLineBreaksBefore() {
this.lineBreaksBefore = 0;
}
public void breakAfter() {
putLineBreaksAfter(1);
}
public void putLineBreaksAfter(int lineBreaks) {
this.lineBreaksAfter = Math.max(this.lineBreaksAfter, lineBreaks);
}
public int getLineBreaksAfter() {
return this.lineBreaksAfter;
}
public void clearLineBreaksAfter() {
this.lineBreaksAfter = 0;
}
public void setPreserveLineBreaksBefore(boolean preserveLineBreaksBefore) {
this.preserveLineBreaksBefore = preserveLineBreaksBefore;
}
public boolean isPreserveLineBreaksBefore() {
return this.preserveLineBreaksBefore;
}
public void setPreserveLineBreaksAfter(boolean preserveLineBreaksAfter) {
this.preserveLineBreaksAfter = preserveLineBreaksAfter;
}
public boolean isPreserveLineBreaksAfter() {
return this.preserveLineBreaksAfter;
}
/** Increases this token's indentation by one position */
public void indent() {
this.indent++;
}
/** Decreses this token's indentation by one position */
public void unindent() {
this.indent--;
}
public void setIndent(int indent) {
assert indent >= 0;
this.indent = indent;
}
public int getIndent() {
return this.indent;
}
public void setEmptyLineIndentAdjustment(int adjustment) {
this.emptyLineIndentAdjustment = adjustment;
}
public int getEmptyLineIndentAdjustment() {
return this.emptyLineIndentAdjustment;
}
public void setAlign(int align) {
this.align = align;
}
public int getAlign() {
return this.align;
}
public void setToEscape(boolean shouldEscape) {
this.toEscape = shouldEscape;
}
public boolean isToEscape() {
return this.toEscape;
}
public void setNextLineOnWrap() {
this.nextLineOnWrap = true;
}
public boolean isNextLineOnWrap() {
return this.nextLineOnWrap;
}
public void setWrapPolicy(WrapPolicy wrapPolicy) {
this.wrapPolicy = wrapPolicy;
}
public WrapPolicy getWrapPolicy() {
return this.wrapPolicy;
}
public boolean isWrappable() {
WrapPolicy wp = this.wrapPolicy;
return wp != null && wp.wrapMode != WrapMode.DISABLED && wp.wrapMode != WrapMode.BLOCK_INDENT;
}
public void setNLSTag(Token nlsTagToken) {
this.nlsTagToken = nlsTagToken;
}
public boolean hasNLSTag() {
return this.nlsTagToken != null;
}
public Token getNLSTag() {
return this.nlsTagToken;
}
public void setInternalStructure(List<Token> internalStructure) {
this.internalStructure = internalStructure;
}
public List<Token> getInternalStructure() {
return this.internalStructure;
}
public boolean isComment() {
switch (this.tokenType) {
case TokenNameCOMMENT_BLOCK:
case TokenNameCOMMENT_JAVADOC:
case TokenNameCOMMENT_LINE:
return true;
}
return false;
}
public String toString(String source) {
return source.substring(this.originalStart, this.originalEnd + 1);
}
public int countChars() {
return this.originalEnd - this.originalStart + 1;
}
/*
* Conceptually, Token abstracts away from the source so it doesn't need to know how
* the source looks like. However, it's useful to see the actual token contents while debugging.
* Uncomment this field, commented code in toString() below and in DefaultCodeFormatter.init(String source)
* during debugging sessions to easily recognize tokens.
*/
// public static String source;
@Override
public String toString() {
// if (source != null) // see comment above
// return toString(source);
return "[" + this.originalStart + "-" + this.originalEnd + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}