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 - [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
/******************************************************************************* * 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.
/** * 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.
/** * 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.
/** Wrap mode for the "Wrap where necessary" policies. */
WHERE_NECESSARY,
Wrap mode for the "Wrap all elements" policies.
/** Wrap mode for the "Wrap all elements" policies. */
TOP_PRIORITY,
Wrap mode for tokens that must be wrapped due to "Force wrap" setting.
/** 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.
/** * 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
/** 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.
/** * 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.
/** 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.
/** 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).
/** Position in source of the last character (this position is included in the token). */
public final int originalEnd;
Type of this token. See TerminalTokens for constants definition.
/** 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
/** Adds space before this token */
public void spaceBefore() { this.spaceBefore = true; }
Removes space before this token
/** Removes space before this token */
public void clearSpaceBefore() { this.spaceBefore = false; } public boolean isSpaceBefore() { return this.spaceBefore; }
Adds space after this token
/** Adds space after this token */
public void spaceAfter() { this.spaceAfter = true; }
Removes space after this token
/** 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.
/** 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
/** Increases this token's indentation by one position */
public void indent() { this.indent++; }
Decreses this token's indentation by one position
/** 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$ } }