| /******************************************************************************* |
| * 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 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.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; |
| } |
| |
| /** 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$ |
| } |
| } |