/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Ray V. (voidstar@gmail.com) - Contribution for bug 282988
*******************************************************************************/
package org.eclipse.che.ide.ext.java.jdt.core.formatter;
import org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation;
import org.eclipse.che.ide.ext.java.jdt.core.compiler.InvalidInputException;
import org.eclipse.che.ide.ext.java.jdt.core.formatter.align.Alignment;
import org.eclipse.che.ide.ext.java.jdt.core.formatter.align.AlignmentException;
import org.eclipse.che.ide.ext.java.jdt.core.formatter.comment.CommentFormatterUtil;
import org.eclipse.che.ide.ext.java.jdt.core.formatter.comment.IJavaDocTagConstants;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ASTVisitor;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast.Annotation;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.parser.Parser;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.parser.Scanner;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.parser.ScannerHelper;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.parser.TerminalTokens;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.util.Util;
import org.eclipse.che.ide.ext.java.jdt.internal.core.util.CodeSnippetParsingUtil;
import org.eclipse.che.ide.ext.java.jdt.internal.core.util.RecordedParsingInformation;
import org.eclipse.che.ide.ext.java.jdt.text.edits.MalformedTreeException;
import org.eclipse.che.ide.ext.java.jdt.text.edits.MultiTextEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.ReplaceEdit;
import org.eclipse.che.ide.ext.java.jdt.text.edits.TextEdit;
import org.eclipse.che.ide.api.text.Region;
import org.eclipse.che.ide.api.text.RegionImpl;
import java.util.Arrays;
import java.util.Comparator;
/** This class is responsible for dumping formatted source */
public class Scribe implements IJavaDocTagConstants {
private static final int INITIAL_SIZE = 100;
private boolean checkLineWrapping;
/** one-based column */
public int column;
private int[][] commentPositions;
// Most specific alignment.
public Alignment currentAlignment;
public int currentToken;
// edits management
private OptimizedReplaceEdit[] edits;
public int editsIndex;
public CodeFormatterVisitor formatter;
public int indentationLevel;
public int lastNumberOfNewLines;
private boolean preserveLineBreakIndentation = false;
public int line;
private int[] lineEnds;
private int maxLines;
public Alignment memberAlignment;
public boolean needSpace = false;
// Line separator infos
final private String lineSeparator;
final private String lineSeparatorAndSpace;
final private char firstLS;
final private int lsLength;
public int nlsTagCounter;
public int pageWidth;
public boolean pendingSpace = false;
public Scanner scanner;
public int scannerEndPosition;
public int tabLength;
public int indentationSize;
private final Region[] regions;
private Region[] adaptedRegions;
public int tabChar;
public int numberOfIndentations;
private boolean useTabsOnlyForLeadingIndents;
/** empty lines */
private final boolean indentEmptyLines;
int blank_lines_between_import_groups = -1;
// Preserve empty lines constants
public static final int DO_NOT_PRESERVE_EMPTY_LINES = -1;
public static final int PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION = 1;
public static final int PRESERVE_EMPTY_LINES_IN_FORMAT_LEFT_CURLY_BRACE = 2;
public static final int PRESERVE_EMPTY_LINES_IN_STRING_LITERAL_CONCATENATION = 3;
public static final int PRESERVE_EMPTY_LINES_IN_CLOSING_ARRAY_INITIALIZER = 4;
public static final int PRESERVE_EMPTY_LINES_IN_FORMAT_OPENING_BRACE = 5;
public static final int PRESERVE_EMPTY_LINES_IN_BINARY_EXPRESSION = 6;
public static final int PRESERVE_EMPTY_LINES_IN_EQUALITY_EXPRESSION = 7;
public static final int PRESERVE_EMPTY_LINES_BEFORE_ELSE = 8;
public static final int PRESERVE_EMPTY_LINES_IN_SWITCH_CASE = 9;
public static final int PRESERVE_EMPTY_LINES_AT_END_OF_METHOD_DECLARATION = 10;
public static final int PRESERVE_EMPTY_LINES_AT_END_OF_BLOCK = 11;
final static int PRESERVE_EMPTY_LINES_DO_NOT_USE_ANY_INDENTATION = -1;
final static int PRESERVE_EMPTY_LINES_USE_CURRENT_INDENTATION = 0;
final static int PRESERVE_EMPTY_LINES_USE_TEMPORARY_INDENTATION = 1;
/** disabling */
boolean editsEnabled;
boolean useTags;
int tagsKind;
/* Comments formatting */
private static final int INCLUDE_BLOCK_COMMENTS = CodeFormatter.F_INCLUDE_COMMENTS
| CodeFormatter.K_MULTI_LINE_COMMENT;
private static final int INCLUDE_JAVA_DOC = CodeFormatter.F_INCLUDE_COMMENTS | CodeFormatter.K_JAVA_DOC;
private static final int INCLUDE_LINE_COMMENTS = CodeFormatter.F_INCLUDE_COMMENTS
| CodeFormatter.K_SINGLE_LINE_COMMENT;
private static final int SKIP_FIRST_WHITESPACE_TOKEN = -2;
private static final int INVALID_TOKEN = 2000;
static final int NO_TRAILING_COMMENT = 0x0000;
static final int BASIC_TRAILING_COMMENT = 0x0100;
static final int COMPLEX_TRAILING_COMMENT = 0x0200;
static final int IMPORT_TRAILING_COMMENT = COMPLEX_TRAILING_COMMENT | 0x0001;
static final int UNMODIFIABLE_TRAILING_COMMENT = 0x0400;
private int formatComments = 0;
private int headerEndPosition = -1;
String commentIndentation; // indentation requested in comments (usually in javadoc root tags description)
// Class to store previous line comment information
static class LineComment {
boolean contiguous = false;
int currentIndentation, indentation;
int lines;
char[] leadingSpaces;
}
final LineComment lastLineComment = new LineComment();
// New way to format javadoc
private FormatterCommentParser formatterCommentParser; // specialized parser to format comments
// Disabling and enabling tags
OptimizedReplaceEdit previousDisabledEdit;
private char[] disablingTag, enablingTag;
// Well know strings
private String[] newEmptyLines = new String[10];
private static String[] COMMENT_INDENTATIONS = new String[20];
// final string buffers
private final StringBuffer tempBuffer = new StringBuffer();
private final StringBuffer blockCommentBuffer = new StringBuffer();
private final StringBuffer blockCommentTokensBuffer = new StringBuffer();
private final StringBuffer codeSnippetBuffer = new StringBuffer();
private final StringBuffer javadocBlockRefBuffer = new StringBuffer();
private final StringBuffer javadocGapLinesBuffer = new StringBuffer();
private StringBuffer[] javadocHtmlTagBuffers = new StringBuffer[5];
private final StringBuffer javadocTextBuffer = new StringBuffer();
private final StringBuffer javadocTokensBuffer = new StringBuffer();
Scribe(CodeFormatterVisitor formatter, long sourceLevel, Region[] regions,
CodeSnippetParsingUtil codeSnippetParsingUtil, boolean includeComments) {
initializeScanner(sourceLevel, formatter.preferences);
this.formatter = formatter;
this.pageWidth = formatter.preferences.page_width;
this.tabLength = formatter.preferences.tab_size;
this.indentationLevel = 0; // initialize properly
this.numberOfIndentations = 0;
this.useTabsOnlyForLeadingIndents = formatter.preferences.use_tabs_only_for_leading_indentations;
this.indentEmptyLines = formatter.preferences.indent_empty_lines;
this.tabChar = formatter.preferences.tab_char;
if (this.tabChar == DefaultCodeFormatterOptions.MIXED) {
this.indentationSize = formatter.preferences.indentation_size;
} else {
this.indentationSize = this.tabLength;
}
this.lineSeparator = formatter.preferences.line_separator;
this.lineSeparatorAndSpace = this.lineSeparator + ' ';
this.firstLS = this.lineSeparator.charAt(0);
this.lsLength = this.lineSeparator.length();
this.indentationLevel = formatter.preferences.initial_indentation_level * this.indentationSize;
this.regions = regions;
if (codeSnippetParsingUtil != null) {
final RecordedParsingInformation information = codeSnippetParsingUtil.recordedParsingInformation;
if (information != null) {
this.lineEnds = information.lineEnds;
this.commentPositions = information.commentPositions;
}
}
if (formatter.preferences.comment_format_line_comment)
this.formatComments |= CodeFormatter.K_SINGLE_LINE_COMMENT;
if (formatter.preferences.comment_format_block_comment)
this.formatComments |= CodeFormatter.K_MULTI_LINE_COMMENT;
if (formatter.preferences.comment_format_javadoc_comment)
this.formatComments |= CodeFormatter.K_JAVA_DOC;
if (includeComments)
this.formatComments |= CodeFormatter.F_INCLUDE_COMMENTS;
reset();
}
/**
* This method will adapt the selected regions if needed. If a region should be adapted (see isAdaptableRegion(IRegion))
* retrieve correct upper and lower bounds and replace the region.
*/
private void adaptRegions() {
int max = this.regions.length;
if (max == 1) {
// It's not necessary to adapt the single region which covers all the source
if (this.regions[0].getOffset() == 0 && this.regions[0].getLength() == this.scannerEndPosition) {
this.adaptedRegions = this.regions;
return;
}
}
this.adaptedRegions = new Region[max];
int commentIndex = 0;
for (int i = 0; i < max; i++) {
Region aRegion = this.regions[i];
int offset = aRegion.getOffset();
int length = aRegion.getLength();
// First look if the region starts or ends inside a comment
int index = getCommentIndex(commentIndex, offset);
int adaptedOffset = offset;
int adaptedLength = length;
if (index >= 0) {
// the offset of the region is inside a comment => restart the region from the comment start
adaptedOffset = this.commentPositions[index][0];
if (adaptedOffset >= 0) {
// adapt only javadoc or block comments. Since fix for bug
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=238210
// edits in line comments only concerns whitespaces hence can be
// treated as edits in code
adaptedLength = length + offset - adaptedOffset;
commentIndex = index;
}
}
index = getCommentIndex(commentIndex, offset + length - 1);
if (index >= 0 && this.commentPositions[index][0] >= 0) { // only javadoc or block comment
// the region end is inside a comment => set the region end at the comment end
int commentEnd = this.commentPositions[index][1];
if (commentEnd < 0)
commentEnd = -commentEnd;
adaptedLength = commentEnd - adaptedOffset;
commentIndex = index;
}
if (adaptedLength != length) {
// adapt the region and jump to next one
this.adaptedRegions[i] = new RegionImpl(adaptedOffset, adaptedLength);
} else {
this.adaptedRegions[i] = aRegion;
}
}
}
/*
* Adapt edits to regions.
* @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=234583" for more details
*/
private void adaptEdits() {
// See if adapting edits is really necessary
int max = this.regions.length;
if (max == 1) {
if (this.regions[0].getOffset() == 0 && this.regions[0].getLength() == this.scannerEndPosition) {
// No need to adapt as the regions covers the whole source
return;
}
}
// Sort edits
OptimizedReplaceEdit[] sortedEdits = new OptimizedReplaceEdit[this.editsIndex];
System.arraycopy(this.edits, 0, sortedEdits, 0, this.editsIndex);
Arrays.sort(sortedEdits, new Comparator() {
public int compare(Object o1, Object o2) {
OptimizedReplaceEdit edit1 = (OptimizedReplaceEdit)o1;
OptimizedReplaceEdit edit2 = (OptimizedReplaceEdit)o2;
return edit1.offset - edit2.offset;
}
});
// Adapt overlapping edits
int currentEdit = -1;
for (int i = 0; i < max; i++) {
Region region = this.adaptedRegions[i];
int offset = region.getOffset();
int length = region.getLength();
// modify overlapping edits on the region (if any)
int index = adaptEdit(sortedEdits, currentEdit, offset, offset + length);
if (index != -1) {
currentEdit = index;
}
}
// Set invalid all edits outside the region
if (currentEdit != -1) {
int length = sortedEdits.length;
for (int e = currentEdit; e < length; e++) {
sortedEdits[e].offset = -1;
}
}
}
/*
* Search whether a region overlap edit(s) at its start and/or at its end. If so, modify the concerned edits to keep only the
* modifications which are inside the given region. The edit modification is done as follow: 1) start it from the region start
* if it overlaps the region's start 2) end it at the region end if it overlaps the region's end 3) remove from the replacement
* string the number of lines which are outside the region: before when overlapping region's start and after when overlapping
* region's end. Note that the trailing indentation of the replacement string is not kept when the region's end is overlapped
* because it's always outside the region.
*/
private int adaptEdit(OptimizedReplaceEdit[] sortedEdits, int start, int regionStart, int regionEnd) {
int initialStart = start == -1 ? 0 : start;
int bottom = initialStart, top = sortedEdits.length - 1;
int topEnd = top;
int i = 0;
OptimizedReplaceEdit edit = null;
int overlapIndex = -1;
// Look for an edit overlapping the region start
while (bottom <= top) {
i = bottom + (top - bottom) / 2;
edit = sortedEdits[i];
int editStart = edit.offset;
int editEnd = editStart + edit.length;
if (editStart > regionStart) { // the edit starts after the region's start => no possible overlap of region's start
top = i - 1;
if (editStart > regionEnd) { // the edit starts after the region's end => no possible overlap of region's end
topEnd = top;
}
} else {
if (editEnd < regionStart) { // the edit ends before the region's start => no possible overlap of region's start
bottom = i + 1;
} else {
// Count the lines of the edit which are outside the region
int linesOutside = 0;
StringBuffer spacesOutside = new StringBuffer();
this.scanner.resetTo(editStart, editEnd - 1);
while (this.scanner.currentPosition < regionStart && !this.scanner.atEnd()) {
char ch = (char)this.scanner.getNextChar();
switch (ch) {
case '\n':
linesOutside++;
spacesOutside.setLength(0);
break;
case '\r':
break;
default:
spacesOutside.append(ch);
break;
}
}
// Restart the edit at the beginning of the line where the region start
edit.offset = regionStart;
int editLength = edit.length;
edit.length -= edit.offset - editStart;
// Cut replacement string if necessary
int length = edit.replacement.length();
if (length > 0) {
// Count the lines in replacement string
int linesReplaced = 0;
for (int idx = 0; idx < length; idx++) {
if (edit.replacement.charAt(idx) == '\n')
linesReplaced++;
}
// If the edit was a replacement but become an insertion due to the length reduction
// and if the edit finishes just before the region starts and if there's no line to replace
// then there's no replacement to do...
if (editLength > 0 && edit.length == 0 && editEnd == regionStart && linesReplaced == 0
&& linesOutside == 0) {
edit.offset = -1;
} else {
// As the edit starts outside the region, remove first lines from edit string if any
if (linesReplaced > 0) {
int linesCount = linesOutside >= linesReplaced ? linesReplaced : linesOutside;
if (linesCount > 0) {
int idx = 0;
loop:
while (idx < length) {
char ch = edit.replacement.charAt(idx);
switch (ch) {
case '\n':
linesCount--;
if (linesCount == 0) {
idx++;
break loop;
}
break;
case '\r':
case ' ':
case '\t':
break;
default:
break loop;
}
idx++;
}
// Compare spaces outside the region and the beginning
// of the replacement string to remove the common part
int spacesOutsideLength = spacesOutside.length();
int replacementStart = idx;
for (int o = 0, r = 0; o < spacesOutsideLength && r < (length - idx); o++) {
char rch = edit.replacement.charAt(idx + r);
char och = spacesOutside.charAt(o);
if (rch == och) {
replacementStart++;
r++;
} else if (rch == '\t' && (this.tabLength > 0 && och == ' ')) {
if ((o + 1) % this.tabLength == 0) {
replacementStart++;
r++;
}
} else {
break;
}
}
// Update the replacement string
if (replacementStart > length || (replacementStart == length && spacesOutsideLength > 0)) {
edit.offset = -1;
} else if (spacesOutsideLength == 0 && replacementStart == length) {
edit.replacement = ""; //$NON-NLS-1$
} else {
edit.replacement = edit.replacement.substring(replacementStart);
}
}
}
}
}
overlapIndex = i;
break;
}
}
}
int validIndex = (overlapIndex != -1) ? overlapIndex : bottom;
// Look for an edit overlapping the region end
if (overlapIndex != -1)
bottom = overlapIndex;
while (bottom <= topEnd) {
i = bottom + (topEnd - bottom) / 2;
edit = sortedEdits[i];
int editStart = edit.offset;
int editEnd = editStart + edit.length;
if (regionEnd < editStart) { // the edit starts after the region's end => no possible overlap of region's end
topEnd = i - 1;
} else if (regionEnd == editStart) { // special case when the edit starts just after the region's end...
// ...we got the last index of the edit inside the region
topEnd = i - 1;
// this last edit is valid only if it's an insertion and if it has indentation
if (edit.length == 0) {
int nrLength = 0;
int rLength = edit.replacement.length();
if (nrLength < rLength) {
int ch = edit.replacement.charAt(nrLength);
loop:
while (nrLength < rLength) {
switch (ch) {
case ' ':
case '\t':
nrLength++;
break;
default:
break loop;
}
}
}
if (nrLength > 0) {
topEnd++;
if (nrLength < rLength) {
edit.replacement = edit.replacement.substring(0, nrLength);
}
}
}
break;
} else if (editEnd <= regionEnd) { // the edit ends before the region's end => no possible overlap of region's end
bottom = i + 1;
} else {
// Count the lines of the edit which are outside the region
int linesOutside = 0;
this.scanner.resetTo(editStart, editEnd - 1);
while (!this.scanner.atEnd()) {
boolean after = this.scanner.currentPosition >= regionEnd;
char ch = (char)this.scanner.getNextChar();
if (ch == '\n') {
if (after)
linesOutside++;
}
}
// Cut replacement string if necessary
int length = edit.replacement.length();
if (length > 0) {
// Count the lines in replacement string
int linesReplaced = 0;
for (int idx = 0; idx < length; idx++) {
if (edit.replacement.charAt(idx) == '\n')
linesReplaced++;
}
// Set the replacement string to the number of missing new lines
// As the end of the edit is out of the region, the possible trailing
// indentation should not be added...
if (linesReplaced == 0) {
edit.replacement = ""; //$NON-NLS-1$
} else {
int linesCount = linesReplaced > linesOutside ? linesReplaced - linesOutside : 0;
if (linesCount == 0) {
edit.replacement = ""; //$NON-NLS-1$
} else {
edit.replacement = getNewLineString(linesCount);
}
}
}
edit.length = regionEnd - editStart;
// We got the last edit of the regions, give up
topEnd = i;
break;
}
}
// Set invalid all edits outside the region
for (int e = initialStart; e < validIndex; e++) {
sortedEdits[e].offset = -1;
}
// Return the index of next edit to look at
return topEnd + 1;
}
private final void addDeleteEdit(int start, int end) {
if (this.edits.length == this.editsIndex) {
// resize
resize();
}
addOptimizedReplaceEdit(start, end - start + 1, Util.EMPTY_STRING);
}
public final void addInsertEdit(int insertPosition, String insertedString) {
if (this.edits.length == this.editsIndex) {
// resize
resize();
}
addOptimizedReplaceEdit(insertPosition, 0, insertedString);
}
private final void addOptimizedReplaceEdit(int offset, int length, String replacement) {
if (!this.editsEnabled) {
if (this.previousDisabledEdit != null && this.previousDisabledEdit.offset == offset) {
replacement = this.previousDisabledEdit.replacement;
}
this.previousDisabledEdit = null;
if (replacement.indexOf(this.lineSeparator) >= 0) {
if (length == 0 || printNewLinesCharacters(offset, length)) {
this.previousDisabledEdit = new OptimizedReplaceEdit(offset, length, replacement);
}
}
return;
}
if (this.editsIndex > 0) {
// try to merge last two edits
final OptimizedReplaceEdit previous = this.edits[this.editsIndex - 1];
final int previousOffset = previous.offset;
final int previousLength = previous.length;
final int endOffsetOfPreviousEdit = previousOffset + previousLength;
final int replacementLength = replacement.length();
final String previousReplacement = previous.replacement;
final int previousReplacementLength = previousReplacement.length();
if (previousOffset == offset && previousLength == length
&& (replacementLength == 0 || previousReplacementLength == 0)) {
if (this.currentAlignment != null) {
final Location location = this.currentAlignment.location;
if (location.editsIndex == this.editsIndex) {
location.editsIndex--;
location.textEdit = previous;
}
}
this.editsIndex--;
return;
}
if (endOffsetOfPreviousEdit == offset) {
if (length != 0) {
if (replacementLength != 0) {
this.edits[this.editsIndex - 1] =
new OptimizedReplaceEdit(previousOffset, previousLength + length, previousReplacement
+ replacement);
} else if (previousLength + length == previousReplacementLength) {
// check the characters. If they are identical, we can get rid of the previous edit
boolean canBeRemoved = true;
loop:
for (int i = previousOffset; i < previousOffset + previousReplacementLength; i++) {
if (this.scanner.source[i] != previousReplacement.charAt(i - previousOffset)) {
this.edits[this.editsIndex - 1] =
new OptimizedReplaceEdit(previousOffset, previousReplacementLength, previousReplacement);
canBeRemoved = false;
break loop;
}
}
if (canBeRemoved) {
if (this.currentAlignment != null) {
final Location location = this.currentAlignment.location;
if (location.editsIndex == this.editsIndex) {
location.editsIndex--;
location.textEdit = previous;
}
}
this.editsIndex--;
}
} else {
this.edits[this.editsIndex - 1] =
new OptimizedReplaceEdit(previousOffset, previousLength + length, previousReplacement);
}
} else {
if (replacementLength != 0) {
this.edits[this.editsIndex - 1] =
new OptimizedReplaceEdit(previousOffset, previousLength, previousReplacement + replacement);
}
}
} else if ((offset + length == previousOffset)
&& (previousLength + length == replacementLength + previousReplacementLength)) {
// check if both edits corresponds to the orignal source code
boolean canBeRemoved = true;
String totalReplacement = replacement + previousReplacement;
loop:
for (int i = 0; i < previousLength + length; i++) {
if (this.scanner.source[i + offset] != totalReplacement.charAt(i)) {
this.edits[this.editsIndex - 1] =
new OptimizedReplaceEdit(offset, previousLength + length, totalReplacement);
canBeRemoved = false;
break loop;
}
}
if (canBeRemoved) {
if (this.currentAlignment != null) {
final Location location = this.currentAlignment.location;
if (location.editsIndex == this.editsIndex) {
location.editsIndex--;
location.textEdit = previous;
}
}
this.editsIndex--;
}
} else {
this.edits[this.editsIndex++] = new OptimizedReplaceEdit(offset, length, replacement);
}
} else {
this.edits[this.editsIndex++] = new OptimizedReplaceEdit(offset, length, replacement);
}
}
public final void addReplaceEdit(int start, int end, String replacement) {
if (this.edits.length == this.editsIndex) {
// resize
resize();
}
addOptimizedReplaceEdit(start, end - start + 1, replacement);
}
public void alignFragment(Alignment alignment, int fragmentIndex) {
alignment.fragmentIndex = fragmentIndex;
alignment.checkColumn();
alignment.performFragmentEffect();
}
public void checkNLSTag(int sourceStart) {
if (hasNLSTag(sourceStart)) {
this.nlsTagCounter++;
}
}
private int consumeInvalidToken(int end) {
this.scanner.resetTo(this.scanner.startPosition, end);
// In case of invalid unicode character, consume the current backslash character before continuing
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=233228
if (this.scanner.currentCharacter == '\\') {
this.scanner.currentPosition = this.scanner.startPosition + 1;
}
int previousPosition = this.scanner.currentPosition;
char ch = (char)this.scanner.getNextChar();
if (this.scanner.atEnd()) {
// avoid infinite loop
return INVALID_TOKEN;
}
while (!this.scanner.atEnd() && ch != '*' && !ScannerHelper.isWhitespace(ch)) {
previousPosition = this.scanner.currentPosition;
ch = (char)this.scanner.getNextChar();
}
// restore last whitespace
this.scanner.currentPosition = previousPosition;
return INVALID_TOKEN;
}
public Alignment createAlignment(int kind, int mode, int count, int sourceRestart) {
return createAlignment(kind, mode, Alignment.R_INNERMOST, count, sourceRestart);
}
public Alignment createAlignment(int kind, int mode, int tieBreakRule, int count, int sourceRestart) {
return createAlignment(kind, mode, tieBreakRule, count, sourceRestart,
this.formatter.preferences.continuation_indentation, false);
}
public Alignment createAlignment(int kind, int mode, int count, int sourceRestart, int continuationIndent,
boolean adjust) {
return createAlignment(kind, mode, Alignment.R_INNERMOST, count, sourceRestart, continuationIndent, adjust);
}
public Alignment createAlignment(int kind, int mode, int tieBreakRule, int count, int sourceRestart,
int continuationIndent, boolean adjust) {
Alignment alignment = new Alignment(kind, mode, tieBreakRule, this, count, sourceRestart, continuationIndent);
// specific break indentation for message arguments inside binary expressions
if ((this.currentAlignment == null && this.formatter.expressionsDepth >= 0)
|| (this.currentAlignment != null && this.currentAlignment.kind == Alignment.BINARY_EXPRESSION &&
(this.formatter.expressionsPos & CodeFormatterVisitor.EXPRESSIONS_POS_MASK) ==
CodeFormatterVisitor.EXPRESSIONS_POS_BETWEEN_TWO)) {
switch (kind) {
case Alignment.CONDITIONAL_EXPRESSION:
case Alignment.MESSAGE_ARGUMENTS:
case Alignment.MESSAGE_SEND:
if (this.formatter.lastBinaryExpressionAlignmentBreakIndentation == alignment.breakIndentationLevel) {
alignment.breakIndentationLevel += this.indentationSize;
alignment.shiftBreakIndentationLevel += this.indentationSize;
this.formatter.lastBinaryExpressionAlignmentBreakIndentation = 0;
}
break;
}
}
// adjust break indentation
if (adjust && this.memberAlignment != null) {
Alignment current = this.memberAlignment;
while (current.enclosing != null) {
current = current.enclosing;
}
if ((current.mode & Alignment.M_MULTICOLUMN) != 0) {
final int indentSize = this.indentationSize;
switch (current.chunkKind) {
case Alignment.CHUNK_METHOD:
case Alignment.CHUNK_TYPE:
if ((mode & Alignment.M_INDENT_BY_ONE) != 0) {
alignment.breakIndentationLevel = this.indentationLevel + indentSize;
} else {
alignment.breakIndentationLevel = this.indentationLevel + continuationIndent * indentSize;
}
alignment.update();
break;
case Alignment.CHUNK_FIELD:
if ((mode & Alignment.M_INDENT_BY_ONE) != 0) {
alignment.breakIndentationLevel = current.originalIndentationLevel + indentSize;
} else {
alignment.breakIndentationLevel =
current.originalIndentationLevel + continuationIndent * indentSize;
}
alignment.update();
break;
}
} else {
switch (current.mode & Alignment.SPLIT_MASK) {
case Alignment.M_COMPACT_SPLIT:
case Alignment.M_COMPACT_FIRST_BREAK_SPLIT:
case Alignment.M_NEXT_PER_LINE_SPLIT:
case Alignment.M_NEXT_SHIFTED_SPLIT:
case Alignment.M_ONE_PER_LINE_SPLIT:
final int indentSize = this.indentationSize;
switch (current.chunkKind) {
case Alignment.CHUNK_METHOD:
case Alignment.CHUNK_TYPE:
if ((mode & Alignment.M_INDENT_BY_ONE) != 0) {
alignment.breakIndentationLevel = this.indentationLevel + indentSize;
} else {
alignment.breakIndentationLevel = this.indentationLevel + continuationIndent * indentSize;
}
alignment.update();
break;
case Alignment.CHUNK_FIELD:
if ((mode & Alignment.M_INDENT_BY_ONE) != 0) {
alignment.breakIndentationLevel = current.originalIndentationLevel + indentSize;
} else {
alignment.breakIndentationLevel =
current.originalIndentationLevel + continuationIndent * indentSize;
}
alignment.update();
break;
}
break;
}
}
}
return alignment;
}
public Alignment createMemberAlignment(int kind, int mode, int count, int sourceRestart) {
Alignment mAlignment = createAlignment(kind, mode, Alignment.R_INNERMOST, count, sourceRestart);
mAlignment.breakIndentationLevel = this.indentationLevel;
return mAlignment;
}
public void enterAlignment(Alignment alignment) {
alignment.enclosing = this.currentAlignment;
alignment.location.lastLocalDeclarationSourceStart = this.formatter.lastLocalDeclarationSourceStart;
this.currentAlignment = alignment;
}
public void enterMemberAlignment(Alignment alignment) {
alignment.enclosing = this.memberAlignment;
alignment.location.lastLocalDeclarationSourceStart = this.formatter.lastLocalDeclarationSourceStart;
this.memberAlignment = alignment;
}
public void exitAlignment(Alignment alignment, boolean discardAlignment) {
Alignment current = this.currentAlignment;
while (current != null) {
if (current == alignment)
break;
current = current.enclosing;
}
if (current == null) {
throw new AbortFormatting("could not find matching alignment: " + alignment); //$NON-NLS-1$
}
this.indentationLevel = alignment.location.outputIndentationLevel;
this.numberOfIndentations = alignment.location.numberOfIndentations;
this.formatter.lastLocalDeclarationSourceStart = alignment.location.lastLocalDeclarationSourceStart;
if (discardAlignment) {
this.currentAlignment = alignment.enclosing;
if (this.currentAlignment == null) {
this.formatter.lastBinaryExpressionAlignmentBreakIndentation = 0;
}
}
}
public void exitMemberAlignment(Alignment alignment) {
Alignment current = this.memberAlignment;
while (current != null) {
if (current == alignment)
break;
current = current.enclosing;
}
if (current == null) {
throw new AbortFormatting("could not find matching alignment: " + alignment); //$NON-NLS-1$
}
this.indentationLevel = current.location.outputIndentationLevel;
this.numberOfIndentations = current.location.numberOfIndentations;
this.formatter.lastLocalDeclarationSourceStart = alignment.location.lastLocalDeclarationSourceStart;
this.memberAlignment = current.enclosing;
}
/**
* Answer actual indentation level based on true column position
*
* @return int
*/
public int getColumnIndentationLevel() {
return this.column - 1;
}
public final int getCommentIndex(int position) {
if (this.commentPositions == null)
return -1;
int length = this.commentPositions.length;
if (length == 0) {
return -1;
}
int g = 0, d = length - 1;
int m = 0;
while (g <= d) {
m = g + (d - g) / 2;
int bound = this.commentPositions[m][1];
if (bound < 0) {
bound = -bound;
}
if (bound < position) {
g = m + 1;
} else if (bound > position) {
d = m - 1;
} else {
return m;
}
}
return -(g + 1);
}
/*
* Returns the index of the comment including the given offset position starting the search from the given start index.
* @param start The start index for the research
* @param position The position
* @return The index of the comment if the given position is located inside it, -1 otherwise
*/
private int getCommentIndex(int start, int position) {
int commentsLength = this.commentPositions == null ? 0 : this.commentPositions.length;
if (commentsLength == 0)
return -1;
if (position == 0) {
if (commentsLength > 0 && this.commentPositions[0][0] == 0) {
return 0;
}
return -1;
}
int bottom = start, top = commentsLength - 1;
int i = 0;
int[] comment = null;
while (bottom <= top) {
i = bottom + (top - bottom) / 2;
comment = this.commentPositions[i];
int commentStart = comment[0];
if (commentStart < 0)
commentStart = -commentStart;
if (position < commentStart) {
top = i - 1;
} else {
int commentEnd = comment[1];
if (commentEnd < 0)
commentEnd = -commentEnd;
if (position >= commentEnd) {
bottom = i + 1;
} else {
return i;
}
}
}
return -1;
}
private int getCurrentCommentIndentation(int start) {
int linePtr = -Arrays.binarySearch(this.lineEnds, start);
int indentation = 0;
int beginningOfLine = getLineEnd(linePtr - 1) + 1;
if (beginningOfLine == -1) {
beginningOfLine = 0;
}
int currentStartPosition = start;
char[] source = this.scanner.source;
// find the position of the beginning of the line containing the comment
while (beginningOfLine > currentStartPosition) {
if (linePtr > 0) {
beginningOfLine = getLineEnd(--linePtr) + 1;
} else {
beginningOfLine = 0;
break;
}
}
for (int i = beginningOfLine; i < currentStartPosition; i++) {
char currentCharacter = source[i];
switch (currentCharacter) {
case '\t':
if (this.tabLength != 0) {
int reminder = indentation % this.tabLength;
if (reminder == 0) {
indentation += this.tabLength;
} else {
indentation = ((indentation / this.tabLength) + 1) * this.tabLength;
}
}
break;
case '\r':
case '\n':
indentation = 0;
break;
default:
indentation++;
break;
}
}
return indentation;
}
int getCurrentIndentation(char[] whitespaces, int offset) {
if (whitespaces == null)
return offset;
int length = whitespaces.length;
if (this.tabLength == 0)
return length;
int indentation = offset;
for (int i = 0; i < length; i++) {
char ch = whitespaces[i];
switch (ch) {
case '\t':
int reminder = indentation % this.tabLength;
if (reminder == 0) {
indentation += this.tabLength;
} else {
indentation = ((indentation / this.tabLength) + 1) * this.tabLength;
}
break;
case '\r':
case '\n':
indentation = 0;
break;
default:
indentation++;
break;
}
}
return indentation;
}
int getCurrentIndentation(int start) {
int linePtr = Arrays.binarySearch(this.lineEnds, start);
if (linePtr < 0) {
linePtr = -linePtr - 1;
}
int indentation = 0;
int beginningOfLine = getLineEnd(linePtr) + 1;
if (beginningOfLine == -1) {
beginningOfLine = 0;
}
char[] source = this.scanner.source;
for (int i = beginningOfLine; i < start; i++) {
char currentCharacter = source[i];
switch (currentCharacter) {
case '\t':
if (this.tabLength != 0) {
int reminder = indentation % this.tabLength;
if (reminder == 0) {
indentation += this.tabLength;
} else {
indentation = ((indentation / this.tabLength) + 1) * this.tabLength;
}
}
break;
case '\r':
case '\n':
indentation = 0;
break;
case ' ':
indentation++;
break;
default:
return indentation;
}
}
return indentation;
}
public String getEmptyLines(int linesNumber) {
if (this.nlsTagCounter > 0) {
return Util.EMPTY_STRING;
}
String emptyLines;
if (this.lastNumberOfNewLines == 0) {
linesNumber++; // add an extra line breaks
if (this.indentEmptyLines) {
this.tempBuffer.setLength(0);
for (int i = 0; i < linesNumber; i++) {
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(this.lineSeparator);
this.column = 1;
}
emptyLines = this.tempBuffer.toString();
} else {
emptyLines = getNewLineString(linesNumber);
}
this.lastNumberOfNewLines += linesNumber;
this.line += linesNumber;
this.column = 1;
this.needSpace = false;
this.pendingSpace = false;
} else if (this.lastNumberOfNewLines == 1) {
if (this.indentEmptyLines) {
this.tempBuffer.setLength(0);
for (int i = 0; i < linesNumber; i++) {
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(this.lineSeparator);
this.column = 1;
}
emptyLines = this.tempBuffer.toString();
} else {
emptyLines = getNewLineString(linesNumber);
}
this.lastNumberOfNewLines += linesNumber;
this.line += linesNumber;
this.column = 1;
this.needSpace = false;
this.pendingSpace = false;
} else {
if ((this.lastNumberOfNewLines - 1) >= linesNumber) {
// there is no need to add new lines
return Util.EMPTY_STRING;
}
final int realNewLineNumber = linesNumber - this.lastNumberOfNewLines + 1;
if (this.indentEmptyLines) {
this.tempBuffer.setLength(0);
for (int i = 0; i < realNewLineNumber; i++) {
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(this.lineSeparator);
this.column = 1;
}
emptyLines = this.tempBuffer.toString();
} else {
emptyLines = getNewLineString(realNewLineNumber);
}
this.lastNumberOfNewLines += realNewLineNumber;
this.line += realNewLineNumber;
this.column = 1;
this.needSpace = false;
this.pendingSpace = false;
}
return emptyLines;
}
public OptimizedReplaceEdit getLastEdit() {
if (this.editsIndex > 0) {
return this.edits[this.editsIndex - 1];
}
return null;
}
public final int getLineEnd(int lineNumber) {
if (this.lineEnds == null)
return -1;
if (lineNumber >= this.lineEnds.length + 1)
return this.scannerEndPosition;
if (lineNumber <= 0)
return -1;
return this.lineEnds[lineNumber - 1]; // next line start one character behind the lineEnd of the previous line
}
Alignment getMemberAlignment() {
return this.memberAlignment;
}
public String getNewLine() {
if (this.nlsTagCounter > 0) {
return Util.EMPTY_STRING;
}
if (this.lastNumberOfNewLines >= 1) {
this.column = 1; // ensure that the scribe is at the beginning of a new line
return Util.EMPTY_STRING;
}
this.line++;
this.lastNumberOfNewLines = 1;
this.column = 1;
this.needSpace = false;
this.pendingSpace = false;
return this.lineSeparator;
}
private String getNewLineString(int linesCount) {
int length = this.newEmptyLines.length;
if (linesCount > length) {
System.arraycopy(this.newEmptyLines, 0, this.newEmptyLines = new String[linesCount + 10], 0, length);
}
String newLineString = this.newEmptyLines[linesCount - 1];
if (newLineString == null) {
this.tempBuffer.setLength(0);
for (int j = 0; j < linesCount; j++) {
this.tempBuffer.append(this.lineSeparator);
}
newLineString = this.tempBuffer.toString();
this.newEmptyLines[linesCount - 1] = newLineString;
}
return newLineString;
}
/** Answer next indentation level based on column estimated position (if column is not indented, then use indentationLevel) */
public int getNextIndentationLevel(int someColumn) {
int indent = someColumn - 1;
if (indent == 0)
return this.indentationLevel;
if (this.tabChar == DefaultCodeFormatterOptions.TAB) {
if (this.useTabsOnlyForLeadingIndents) {
return indent;
}
if (this.indentationSize == 0) {
return indent;
}
int rem = indent % this.indentationSize;
int addition = rem == 0 ? 0 : this.indentationSize - rem; // round to superior
return indent + addition;
}
return indent;
}
/* Preserve empty lines depending on given count and preferences. */
private String getPreserveEmptyLines(int count, int emptyLinesRules) {
if (count == 0) {
int currentIndentationLevel = this.indentationLevel;
int useAlignmentBreakIndentation = useAlignmentBreakIndentation(emptyLinesRules);
switch (useAlignmentBreakIndentation) {
case PRESERVE_EMPTY_LINES_DO_NOT_USE_ANY_INDENTATION:
return Util.EMPTY_STRING;
default:
// Return the new indented line
StringBuffer buffer = new StringBuffer(getNewLine());
printIndentationIfNecessary(buffer);
if (useAlignmentBreakIndentation == PRESERVE_EMPTY_LINES_USE_TEMPORARY_INDENTATION) {
this.indentationLevel = currentIndentationLevel;
}
return buffer.toString();
}
}
if (this.blank_lines_between_import_groups >= 0) {
useAlignmentBreakIndentation(emptyLinesRules);
return getEmptyLines(this.blank_lines_between_import_groups);
}
if (this.formatter.preferences.number_of_empty_lines_to_preserve != 0) {
useAlignmentBreakIndentation(emptyLinesRules);
int linesToPreserve = Math.min(count, this.formatter.preferences.number_of_empty_lines_to_preserve);
return getEmptyLines(linesToPreserve);
}
return getNewLine();
}
private int useAlignmentBreakIndentation(int emptyLinesRules) {
// preserve line breaks in wrapping if specified
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=198074
boolean specificEmptyLinesRule = emptyLinesRules != PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION;
if ((this.currentAlignment != null || specificEmptyLinesRule) && !this.formatter.preferences.join_wrapped_lines) {
// insert a new line only if it has not been already done before
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=283476
if (this.lastNumberOfNewLines == 0 || specificEmptyLinesRule || this.formatter.arrayInitializersDepth >= 0) {
// Do not use alignment break indentation in specific circumstances
boolean useAlignmentBreakIndentation;
boolean useAlignmentShiftBreakIndentation = false;
boolean useLastBinaryExpressionAlignmentBreakIndentation = false;
switch (emptyLinesRules) {
case DO_NOT_PRESERVE_EMPTY_LINES:
case PRESERVE_EMPTY_LINES_IN_SWITCH_CASE:
case PRESERVE_EMPTY_LINES_AT_END_OF_METHOD_DECLARATION:
case PRESERVE_EMPTY_LINES_AT_END_OF_BLOCK:
return PRESERVE_EMPTY_LINES_DO_NOT_USE_ANY_INDENTATION;
case PRESERVE_EMPTY_LINES_IN_BINARY_EXPRESSION:
useAlignmentBreakIndentation = true;
if ((this.formatter.expressionsPos & CodeFormatterVisitor.EXPRESSIONS_POS_MASK) ==
CodeFormatterVisitor.EXPRESSIONS_POS_BETWEEN_TWO) {
// we're just before the left expression, try to use the last
// binary expression break indentation if any
useLastBinaryExpressionAlignmentBreakIndentation = true;
}
break;
case PRESERVE_EMPTY_LINES_IN_EQUALITY_EXPRESSION:
useAlignmentShiftBreakIndentation =
this.currentAlignment == null || this.currentAlignment.kind == Alignment.BINARY_EXPRESSION;
useAlignmentBreakIndentation = !useAlignmentShiftBreakIndentation;
break;
case PRESERVE_EMPTY_LINES_IN_FORMAT_OPENING_BRACE:
useAlignmentBreakIndentation =
this.formatter.arrayInitializersDepth <= 1 && this.currentAlignment != null
&& this.currentAlignment.kind == Alignment.ARRAY_INITIALIZER;
break;
case PRESERVE_EMPTY_LINES_IN_FORMAT_LEFT_CURLY_BRACE:
useAlignmentBreakIndentation = false;
break;
default:
if ((emptyLinesRules & 0xFFFF) == PRESERVE_EMPTY_LINES_IN_CLOSING_ARRAY_INITIALIZER
&& this.scanner.currentCharacter == '}') {
// last array initializer closing brace
this.indentationLevel = emptyLinesRules >> 16;
this.preserveLineBreakIndentation = true;
return PRESERVE_EMPTY_LINES_USE_CURRENT_INDENTATION;
}
useAlignmentBreakIndentation = true;
break;
}
// If there's an alignment try to align on its break indentation level
Alignment alignment = this.currentAlignment;
if (alignment == null) {
if (useLastBinaryExpressionAlignmentBreakIndentation) {
if (this.indentationLevel < this.formatter.lastBinaryExpressionAlignmentBreakIndentation) {
this.indentationLevel = this.formatter.lastBinaryExpressionAlignmentBreakIndentation;
}
}
if (useAlignmentShiftBreakIndentation && this.memberAlignment != null) {
if (this.indentationLevel < this.memberAlignment.shiftBreakIndentationLevel) {
this.indentationLevel = this.memberAlignment.shiftBreakIndentationLevel;
}
}
} else {
// Use the member alignment break indentation level when
// it's closer from the wrapped line than the current alignment
if (this.memberAlignment != null
&& this.memberAlignment.location.inputOffset > alignment.location.inputOffset) {
alignment = this.memberAlignment;
}
// Use the break indentation level if possible...
if (useLastBinaryExpressionAlignmentBreakIndentation) {
if (this.indentationLevel < this.formatter.lastBinaryExpressionAlignmentBreakIndentation) {
this.indentationLevel = this.formatter.lastBinaryExpressionAlignmentBreakIndentation;
}
}
if (useAlignmentBreakIndentation) {
if (this.indentationLevel < alignment.breakIndentationLevel) {
this.indentationLevel = alignment.breakIndentationLevel;
}
} else if (useAlignmentShiftBreakIndentation) {
if (this.indentationLevel < alignment.shiftBreakIndentationLevel) {
this.indentationLevel = alignment.shiftBreakIndentationLevel;
}
}
}
this.preserveLineBreakIndentation = true;
if (useLastBinaryExpressionAlignmentBreakIndentation || useAlignmentShiftBreakIndentation) {
return PRESERVE_EMPTY_LINES_USE_TEMPORARY_INDENTATION;
}
return PRESERVE_EMPTY_LINES_USE_CURRENT_INDENTATION;
}
}
return PRESERVE_EMPTY_LINES_DO_NOT_USE_ANY_INDENTATION;
}
public TextEdit getRootEdit() {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=208541
adaptRegions();
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=234583
adaptEdits();
MultiTextEdit edit = null;
int regionsLength = this.adaptedRegions.length;
int textRegionStart;
int textRegionEnd;
if (regionsLength == 1) {
Region lastRegion = this.adaptedRegions[0];
textRegionStart = lastRegion.getOffset();
textRegionEnd = textRegionStart + lastRegion.getLength();
} else {
textRegionStart = this.adaptedRegions[0].getOffset();
Region lastRegion = this.adaptedRegions[regionsLength - 1];
textRegionEnd = lastRegion.getOffset() + lastRegion.getLength();
}
int length = textRegionEnd - textRegionStart + 1;
if (textRegionStart <= 0) {
if (length <= 0) {
edit = new MultiTextEdit(0, 0);
} else {
edit = new MultiTextEdit(0, textRegionEnd);
}
} else {
edit = new MultiTextEdit(textRegionStart, length - 1);
}
for (int i = 0, max = this.editsIndex; i < max; i++) {
OptimizedReplaceEdit currentEdit = this.edits[i];
if (currentEdit.offset >= 0 && currentEdit.offset <= this.scannerEndPosition) {
if (currentEdit.length == 0
|| (currentEdit.offset != this.scannerEndPosition && isMeaningfulEdit(currentEdit))) {
try {
edit.addChild(new ReplaceEdit(currentEdit.offset, currentEdit.length, currentEdit.replacement));
} catch (MalformedTreeException ex) {
// log exception in case of error
CommentFormatterUtil.log(ex);
throw ex;
}
}
}
}
this.edits = null;
return edit;
}
public void handleLineTooLong() {
if (this.formatter.preferences.wrap_outer_expressions_when_nested) {
handleLineTooLongSmartly();
return;
}
// search for closest breakable alignment, using tiebreak rules
// look for outermost breakable one
int relativeDepth = 0, outerMostDepth = -1;
Alignment targetAlignment = this.currentAlignment;
while (targetAlignment != null) {
if (targetAlignment.tieBreakRule == Alignment.R_OUTERMOST && targetAlignment.couldBreak()) {
outerMostDepth = relativeDepth;
}
targetAlignment = targetAlignment.enclosing;
relativeDepth++;
}
if (outerMostDepth >= 0) {
throw new AlignmentException(AlignmentException.LINE_TOO_LONG, outerMostDepth);
}
// look for innermost breakable one
relativeDepth = 0;
targetAlignment = this.currentAlignment;
while (targetAlignment != null) {
if (targetAlignment.couldBreak()) {
throw new AlignmentException(AlignmentException.LINE_TOO_LONG, relativeDepth);
}
targetAlignment = targetAlignment.enclosing;
relativeDepth++;
}
// did not find any breakable location - proceed
}
private void handleLineTooLongSmartly() {
// search for closest breakable alignment, using tiebreak rules
// look for outermost breakable one
int relativeDepth = 0, outerMostDepth = -1;
Alignment targetAlignment = this.currentAlignment;
int previousKind = -1;
int insideMessage = 0;
boolean insideStringConcat = false;
while (targetAlignment != null) {
boolean couldBreak =
targetAlignment.tieBreakRule == Alignment.R_OUTERMOST
|| (!insideStringConcat && insideMessage > 0 && targetAlignment.kind == Alignment.MESSAGE_ARGUMENTS && (!targetAlignment
.wasReset() || previousKind != Alignment.MESSAGE_SEND));
if (couldBreak && targetAlignment.couldBreak()) {
outerMostDepth = relativeDepth;
}
switch (targetAlignment.kind) {
case Alignment.MESSAGE_ARGUMENTS:
case Alignment.MESSAGE_SEND:
insideMessage++;
break;
case Alignment.STRING_CONCATENATION:
insideStringConcat = true;
break;
}
previousKind = targetAlignment.kind;
targetAlignment = targetAlignment.enclosing;
relativeDepth++;
}
if (outerMostDepth >= 0) {
throw new AlignmentException(AlignmentException.LINE_TOO_LONG, outerMostDepth);
}
// look for innermost breakable one
relativeDepth = 0;
targetAlignment = this.currentAlignment;
AlignmentException alignmentException = null;
int msgArgsDepth = -1;
while (targetAlignment != null) {
if (targetAlignment.kind == Alignment.MESSAGE_ARGUMENTS) {
msgArgsDepth = relativeDepth;
}
if (alignmentException == null) {
if (targetAlignment.couldBreak()) {
// do not throw the exception immediately to have a chance to reset
// previously broken alignments (see bug 203588)
alignmentException = new AlignmentException(AlignmentException.LINE_TOO_LONG, relativeDepth);
if (insideStringConcat)
throw alignmentException;
}
} else if (targetAlignment.wasSplit) {
// reset the nearest already broken outermost alignment.
// Note that it's not done twice to avoid infinite loop while raising
// the exception on an innermost alignment...
if (!targetAlignment.wasReset()) {
targetAlignment.reset();
if (msgArgsDepth > alignmentException.relativeDepth) {
alignmentException.relativeDepth = msgArgsDepth;
}
throw alignmentException;
}
}
targetAlignment = targetAlignment.enclosing;
relativeDepth++;
}
if (alignmentException != null) {
throw alignmentException;
}
// did not find any breakable location - proceed
if (this.currentAlignment != null) {
this.currentAlignment.blockAlign = false;
this.currentAlignment.tooLong = true;
}
}
/*
* Check if there is a NLS tag on this line. If yes, return true, returns false otherwise.
*/
private boolean hasNLSTag(int sourceStart) {
// search the last comment where commentEnd < current lineEnd
if (this.lineEnds == null)
return false;
int index = Arrays.binarySearch(this.lineEnds, sourceStart);
int currentLineEnd = getLineEnd(-index);
if (currentLineEnd != -1) {
int commentIndex = getCommentIndex(currentLineEnd);
if (commentIndex < 0) {
commentIndex = -commentIndex - 2;
}
if (commentIndex >= 0 && commentIndex < this.commentPositions.length) {
int start = this.commentPositions[commentIndex][0];
if (start < 0) {
start = -start;
// check that we are on the same line
int lineIndexForComment = Arrays.binarySearch(this.lineEnds, start);
if (lineIndexForComment == index) {
return CharOperation.indexOf(Scanner.TAG_PREFIX, this.scanner.source, true, start, currentLineEnd) != -1;
}
}
}
}
return false;
}
private boolean includesBlockComments() {
return ((this.formatComments & INCLUDE_BLOCK_COMMENTS) == INCLUDE_BLOCK_COMMENTS &&
this.headerEndPosition < this.scanner.currentPosition)
|| (this.formatter.preferences.comment_format_header && this.headerEndPosition >= this.scanner.currentPosition);
}
private boolean includesJavadocComments() {
return ((this.formatComments & INCLUDE_JAVA_DOC) == INCLUDE_JAVA_DOC && this.headerEndPosition < this.scanner.currentPosition)
|| (this.formatter.preferences.comment_format_header && this.headerEndPosition >= this.scanner.currentPosition);
}
private boolean includesLineComments() {
return ((this.formatComments & INCLUDE_LINE_COMMENTS) == INCLUDE_LINE_COMMENTS &&
this.headerEndPosition < this.scanner.currentPosition)
|| (this.formatter.preferences.comment_format_header && this.headerEndPosition >= this.scanner.currentPosition);
}
boolean includesComments() {
return (this.formatComments & CodeFormatter.F_INCLUDE_COMMENTS) != 0;
}
public void indent() {
this.indentationLevel += this.indentationSize;
this.numberOfIndentations++;
}
void setIndentation(int level, int n) {
this.indentationLevel = level + n * this.indentationSize;
this.numberOfIndentations = this.indentationLevel / this.indentationSize;
}
private void initializeScanner(long sourceLevel, DefaultCodeFormatterOptions preferences) {
this.useTags = preferences.use_tags;
this.tagsKind = 0;
char[][] taskTags = null;
if (this.useTags) {
this.disablingTag = preferences.disabling_tag;
this.enablingTag = preferences.enabling_tag;
if (this.disablingTag == null) {
if (this.enablingTag != null) {
taskTags = new char[][]{this.enablingTag};
}
} else if (this.enablingTag == null) {
taskTags = new char[][]{this.disablingTag};
} else {
taskTags = new char[][]{this.disablingTag, this.enablingTag};
}
}
if (taskTags != null) {
loop:
for (int i = 0, length = taskTags.length; i < length; i++) {
if (taskTags[i].length > 2 && taskTags[i][0] == '/') {
switch (taskTags[i][1]) {
case '/':
this.tagsKind = TerminalTokens.TokenNameCOMMENT_LINE;
break loop;
case '*':
if (taskTags[i][2] != '*') {
this.tagsKind = TerminalTokens.TokenNameCOMMENT_BLOCK;
break loop;
}
break;
}
}
}
}
this.scanner =
new Scanner(true, true, false/* nls */, sourceLevel/* sourceLevel */, taskTags, null/* taskPriorities */,
true/* taskCaseSensitive */);
this.editsEnabled = true;
}
private void initFormatterCommentParser() {
if (this.formatterCommentParser == null) {
this.formatterCommentParser = new FormatterCommentParser(this.scanner.sourceLevel);
}
this.formatterCommentParser.scanner.setSource(this.scanner.source);
this.formatterCommentParser.source = this.scanner.source;
this.formatterCommentParser.scanner.lineEnds = this.lineEnds;
this.formatterCommentParser.scanner.linePtr = this.maxLines;
this.formatterCommentParser.parseHtmlTags = this.formatter.preferences.comment_format_html;
}
private boolean isOnFirstColumn(int start) {
if (this.lineEnds == null)
return start == 0;
int index = Arrays.binarySearch(this.lineEnds, start);
// we want the line end of the previous line
int previousLineEnd = getLineEnd(-index - 1);
return previousLineEnd != -1 && previousLineEnd == start - 1;
}
private boolean isMeaningfulEdit(OptimizedReplaceEdit edit) {
final int editLength = edit.length;
final int editReplacementLength = edit.replacement.length();
final int editOffset = edit.offset;
if (editReplacementLength != 0 && editLength == editReplacementLength) {
for (int i = editOffset, max = editOffset + editLength; i < max; i++) {
if (this.scanner.source[i] != edit.replacement.charAt(i - editOffset)) {
return true;
}
}
return false;
}
return true;
}
private void preserveEmptyLines(int count, int insertPosition) {
if (count > 0) {
if (this.blank_lines_between_import_groups >= 0) {
printEmptyLines(this.blank_lines_between_import_groups, insertPosition);
} else if (this.formatter.preferences.number_of_empty_lines_to_preserve != 0) {
int linesToPreserve = Math.min(count, this.formatter.preferences.number_of_empty_lines_to_preserve);
printEmptyLines(linesToPreserve, insertPosition);
} else {
printNewLine(insertPosition);
}
}
}
private void print(int length, boolean considerSpaceIfAny) {
if (this.checkLineWrapping && length + this.column > this.pageWidth) {
handleLineTooLong();
}
this.lastNumberOfNewLines = 0;
if (this.indentationLevel != 0) {
printIndentationIfNecessary();
}
if (considerSpaceIfAny) {
space();
}
if (this.pendingSpace) {
addInsertEdit(this.scanner.getCurrentTokenStartPosition(), " "); //$NON-NLS-1$
}
this.pendingSpace = false;
this.column += length;
this.needSpace = true;
}
private void printBlockComment(boolean isJavadoc) {
int currentTokenStartPosition = this.scanner.getCurrentTokenStartPosition();
int currentTokenEndPosition = this.scanner.getCurrentTokenEndPosition() + 1;
boolean includesBlockComments = !isJavadoc && includesBlockComments();
this.scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition - 1);
int currentCharacter;
boolean isNewLine = false;
int start = currentTokenStartPosition;
int nextCharacterStart = currentTokenStartPosition;
int previousStart = currentTokenStartPosition;
boolean onFirstColumn = isOnFirstColumn(start);
boolean indentComment = false;
if (this.indentationLevel != 0) {
if (isJavadoc || !this.formatter.preferences.never_indent_block_comments_on_first_column || !onFirstColumn) {
indentComment = true;
printIndentationIfNecessary();
}
}
if (this.pendingSpace) {
addInsertEdit(currentTokenStartPosition, " "); //$NON-NLS-1$
}
this.needSpace = false;
this.pendingSpace = false;
int commentColumn = this.column;
if (includesBlockComments) {
if (printBlockComment(currentTokenStartPosition, currentTokenEndPosition)) {
return;
}
}
int currentIndentationLevel = this.indentationLevel;
if ((commentColumn - 1) > this.indentationLevel) {
this.indentationLevel = commentColumn - 1;
}
int currentCommentIndentation = onFirstColumn ? 0 : getCurrentCommentIndentation(start);
boolean formatComment =
(isJavadoc && (this.formatComments & CodeFormatter.K_JAVA_DOC) != 0)
|| (!isJavadoc && (this.formatComments & CodeFormatter.K_MULTI_LINE_COMMENT) != 0);
try {
while (nextCharacterStart <= currentTokenEndPosition && (currentCharacter = this.scanner.getNextChar()) != -1) {
nextCharacterStart = this.scanner.currentPosition;
switch (currentCharacter) {
case '\r':
start = previousStart;
isNewLine = true;
if (this.scanner.getNextChar('\n')) {
currentCharacter = '\n';
nextCharacterStart = this.scanner.currentPosition;
}
break;
case '\n':
start = previousStart;
isNewLine = true;
nextCharacterStart = this.scanner.currentPosition;
break;
default:
if (isNewLine) {
this.column = 1;
this.line++;
isNewLine = false;
boolean addSpace = false;
if (onFirstColumn) {
if (formatComment) {
if (ScannerHelper.isWhitespace((char)currentCharacter)) {
int previousStartPosition = this.scanner.currentPosition;
while (currentCharacter != -1 && currentCharacter != '\r' && currentCharacter != '\n'
&& ScannerHelper.isWhitespace((char)currentCharacter)) {
previousStart = nextCharacterStart;
previousStartPosition = this.scanner.currentPosition;
currentCharacter = this.scanner.getNextChar();
nextCharacterStart = this.scanner.currentPosition;
}
if (currentCharacter == '\r' || currentCharacter == '\n') {
nextCharacterStart = previousStartPosition;
}
}
if (currentCharacter != '\r' && currentCharacter != '\n') {
addSpace = true;
}
}
} else {
if (ScannerHelper.isWhitespace((char)currentCharacter)) {
int previousStartPosition = this.scanner.currentPosition;
int currentIndentation = 0;
loop:
while (currentCharacter != -1 && currentCharacter != '\r' && currentCharacter != '\n'
&& ScannerHelper.isWhitespace((char)currentCharacter)) {
if (currentIndentation >= currentCommentIndentation) {
break loop;
}
previousStart = nextCharacterStart;
previousStartPosition = this.scanner.currentPosition;
switch (currentCharacter) {
case '\t':
if (this.tabLength != 0) {
int reminder = currentIndentation % this.tabLength;
if (reminder == 0) {
currentIndentation += this.tabLength;
} else {
currentIndentation =
((currentIndentation / this.tabLength) + 1) * this.tabLength;
}
}
break;
default:
currentIndentation++;
}
currentCharacter = this.scanner.getNextChar();
nextCharacterStart = this.scanner.currentPosition;
}
if (currentCharacter == '\r' || currentCharacter == '\n') {
nextCharacterStart = previousStartPosition;
}
}
if (formatComment) {
int previousStartTemp = previousStart;
int nextCharacterStartTemp = nextCharacterStart;
while (currentCharacter != -1 && currentCharacter != '\r' && currentCharacter != '\n'
&& ScannerHelper.isWhitespace((char)currentCharacter)) {
previousStart = nextCharacterStart;
currentCharacter = this.scanner.getNextChar();
nextCharacterStart = this.scanner.currentPosition;
}
if (currentCharacter == '*') {
addSpace = true;
} else {
previousStart = previousStartTemp;
nextCharacterStart = nextCharacterStartTemp;
}
this.scanner.currentPosition = nextCharacterStart;
}
}
String replacement;
if (indentComment) {
this.tempBuffer.setLength(0);
this.tempBuffer.append(this.lineSeparator);
if (this.indentationLevel > 0) {
printIndentationIfNecessary(this.tempBuffer);
}
if (addSpace) {
this.tempBuffer.append(' ');
}
replacement = this.tempBuffer.toString();
} else {
replacement = addSpace ? this.lineSeparatorAndSpace : this.lineSeparator;
}
addReplaceEdit(start, previousStart - 1, replacement);
} else {
this.column += (nextCharacterStart - previousStart);
}
}
previousStart = nextCharacterStart;
this.scanner.currentPosition = nextCharacterStart;
}
} finally {
this.indentationLevel = currentIndentationLevel;
}
this.lastNumberOfNewLines = 0;
this.needSpace = false;
this.scanner.resetTo(currentTokenEndPosition, this.scannerEndPosition - 1);
}
private boolean printBlockComment(int currentTokenStartPosition, int currentTokenEndPosition) {
// Compute indentation
int maxColumn = this.formatter.preferences.comment_line_length + 1;
int indentLevel = this.indentationLevel;
int indentations = this.numberOfIndentations;
switch (this.tabChar) {
case DefaultCodeFormatterOptions.TAB:
switch (this.tabLength) {
case 0:
this.indentationLevel = 0;
this.column = 1;
this.numberOfIndentations = 0;
break;
case 1:
this.indentationLevel = this.column - 1;
this.numberOfIndentations = this.indentationLevel;
break;
default:
this.indentationLevel = (this.column / this.tabLength) * this.tabLength;
this.column = this.indentationLevel + 1;
this.numberOfIndentations = this.indentationLevel / this.tabLength;
}
break;
case DefaultCodeFormatterOptions.MIXED:
if (this.tabLength == 0) {
this.indentationLevel = 0;
this.column = 1;
this.numberOfIndentations = 0;
} else {
this.indentationLevel = this.column - 1;
this.numberOfIndentations = this.indentationLevel / this.tabLength;
}
break;
case DefaultCodeFormatterOptions.SPACE:
if (this.indentationSize == 0) {
this.indentationLevel = 0;
this.column = 1;
this.numberOfIndentations = 0;
} else {
this.indentationLevel = this.column - 1;
}
break;
}
// Consume the comment prefix
this.blockCommentBuffer.setLength(0);
this.scanner.getNextChar();
this.scanner.getNextChar();
this.column += 2;
this.scanner.skipComments = true;
this.blockCommentTokensBuffer.setLength(0);
int editStart = this.scanner.currentPosition;
int editEnd = -1;
// Consume text token per token
int previousToken = -1;
boolean newLine = false;
boolean multiLines = false;
boolean hasMultiLines = false;
boolean hasTokens = false;
boolean bufferHasTokens = false;
boolean bufferHasNewLine = false;
boolean lineHasTokens = false;
int hasTextOnFirstLine = 0;
boolean firstWord = true;
boolean clearBlankLines = this.formatter.preferences.comment_clear_blank_lines_in_block_comment;
boolean joinLines = this.formatter.preferences.join_lines_in_comments;
boolean newLinesAtBoundaries = this.formatter.preferences.comment_new_lines_at_block_boundaries;
int scannerLine = Util.getLineNumber(this.scanner.currentPosition, this.lineEnds, 0, this.maxLines);
int firstLine = scannerLine;
int lineNumber = scannerLine;
int lastTextLine = -1;
while (!this.scanner.atEnd()) {
// Consume token
int token;
try {
token = this.scanner.getNextToken();
} catch (InvalidInputException iie) {
token = consumeInvalidToken(currentTokenEndPosition - 1);
newLine = false;
}
// Look at specific tokens
boolean insertSpace = (previousToken == TerminalTokens.TokenNameWHITESPACE) && (!firstWord || !hasTokens);
boolean isTokenStar = false;
switch (token) {
case TerminalTokens.TokenNameWHITESPACE:
if (this.blockCommentTokensBuffer.length() > 0) {
if (hasTextOnFirstLine == 1 && multiLines) {
printBlockCommentHeaderLine(this.blockCommentBuffer);
hasTextOnFirstLine = -1;
}
this.blockCommentBuffer.append(this.blockCommentTokensBuffer);
this.column += this.blockCommentTokensBuffer.length();
this.blockCommentTokensBuffer.setLength(0);
bufferHasTokens = true;
bufferHasNewLine = false;
}
if (previousToken == -1) {
// do not remember the first whitespace
previousToken = SKIP_FIRST_WHITESPACE_TOKEN;
} else {
previousToken = token;
}
lineNumber =
Util.getLineNumber(this.scanner.currentPosition, this.lineEnds,
scannerLine > 1 ? scannerLine - 2 : 0, this.maxLines);
if (lineNumber > scannerLine) {
hasMultiLines = true;
newLine = true;
}
scannerLine = lineNumber;
continue;
case TerminalTokens.TokenNameMULTIPLY:
isTokenStar = true;
lineNumber =
Util.getLineNumber(this.scanner.currentPosition, this.lineEnds,
scannerLine > 1 ? scannerLine - 2 : 0, this.maxLines);
if (lineNumber == firstLine && previousToken == SKIP_FIRST_WHITESPACE_TOKEN) {
this.blockCommentBuffer.append(' ');
}
previousToken = token;
if (this.scanner.currentCharacter == '/') {
editEnd = this.scanner.startPosition - 1;
// Add remaining buffered tokens
if (this.blockCommentTokensBuffer.length() > 0) {
this.blockCommentBuffer.append(this.blockCommentTokensBuffer);
this.column += this.blockCommentTokensBuffer.length();
}
// end of comment
if (newLinesAtBoundaries) {
if (multiLines || hasMultiLines) {
this.blockCommentBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.blockCommentBuffer);
}
}
this.blockCommentBuffer.append(' ');
this.column += BLOCK_FOOTER_LENGTH + 1;
this.scanner.getNextChar(); // reach the end of scanner
continue;
}
if (newLine) {
scannerLine = lineNumber;
newLine = false;
continue;
}
break;
case TerminalTokens.TokenNameMULTIPLY_EQUAL:
if (newLine) {
this.scanner.resetTo(this.scanner.startPosition, currentTokenEndPosition - 1);
this.scanner.getNextChar(); // consume the multiply
previousToken = TerminalTokens.TokenNameMULTIPLY;
scannerLine =
Util.getLineNumber(this.scanner.currentPosition, this.lineEnds, scannerLine > 1 ? scannerLine - 2
: 0, this.maxLines);
continue;
}
break;
case TerminalTokens.TokenNameMINUS:
case TerminalTokens.TokenNameMINUS_MINUS:
if (previousToken == -1) {
// Do not format comment starting with /*-
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=230944
this.indentationLevel = indentLevel;
this.numberOfIndentations = indentations;
this.lastNumberOfNewLines = 0;
this.needSpace = false;
this.scanner.skipComments = false;
this.scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition - 1);
return false;
}
break;
default:
// do nothing
break;
}
// Look at gap and insert corresponding lines if necessary
int linesGap;
int max;
lineNumber =
Util.getLineNumber(this.scanner.currentPosition, this.lineEnds, scannerLine > 1 ? scannerLine - 2 : 0,
this.maxLines);
if (lastTextLine == -1) {
linesGap = newLinesAtBoundaries ? lineNumber - firstLine : 0;
max = 0;
} else {
linesGap = lineNumber - lastTextLine;
if (token == TerminalTokens.TokenNameAT && linesGap == 1) {
// insert one blank line before root tags
linesGap = 2;
}
max = joinLines && lineHasTokens ? 1 : 0;
}
if (linesGap > max) {
if (clearBlankLines) {
// TODO (frederic) see if there's a bug for the unremoved blank line for root tags
if (token == TerminalTokens.TokenNameAT) {
linesGap = 1;
} else {
linesGap = (max == 0 || !joinLines) ? 1 : 0;
}
}
for (int i = 0; i < linesGap; i++) {
// Add remaining buffered tokens
if (this.blockCommentTokensBuffer.length() > 0) {
if (hasTextOnFirstLine == 1) {
printBlockCommentHeaderLine(this.blockCommentBuffer);
hasTextOnFirstLine = -1;
}
this.blockCommentBuffer.append(this.blockCommentTokensBuffer);
this.blockCommentTokensBuffer.setLength(0);
bufferHasTokens = true;
}
this.blockCommentBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.blockCommentBuffer);
this.blockCommentBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
firstWord = true;
multiLines = true;
bufferHasNewLine = true;
}
insertSpace = insertSpace && linesGap == 0;
}
if (newLine)
lineHasTokens = false;
// Increment column
int tokenStart = this.scanner.getCurrentTokenStartPosition();
int tokenLength =
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - tokenStart;
hasTokens = true;
if (!isTokenStar)
lineHasTokens = true;
if (hasTextOnFirstLine == 0 && !isTokenStar) {
if (firstLine == lineNumber) {
hasTextOnFirstLine = 1;
this.column++; // include first space
} else {
hasTextOnFirstLine = -1;
}
}
int lastColumn = this.column + this.blockCommentTokensBuffer.length() + tokenLength;
if (insertSpace)
lastColumn++;
// Append next token inserting a new line if max line is reached
if (lineHasTokens && !firstWord && lastColumn > maxColumn) {
String tokensString = this.blockCommentTokensBuffer.toString().trim();
int tokensStringLength = tokensString.length();
// not enough space on the line
if (hasTextOnFirstLine == 1) {
printBlockCommentHeaderLine(this.blockCommentBuffer);
}
if ((this.indentationLevel + tokensStringLength + tokenLength) > maxColumn) {
// there won't be enough room even if we break the line before the buffered tokens
// So add the buffered tokens now
this.blockCommentBuffer.append(this.blockCommentTokensBuffer);
this.column += this.blockCommentTokensBuffer.length();
this.blockCommentTokensBuffer.setLength(0);
bufferHasNewLine = false;
bufferHasTokens = true;
}
if (bufferHasTokens && !bufferHasNewLine) {
this.blockCommentBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.blockCommentBuffer);
this.blockCommentBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
}
if (this.blockCommentTokensBuffer.length() > 0) {
this.blockCommentBuffer.append(tokensString);
this.column += tokensStringLength;
this.blockCommentTokensBuffer.setLength(0);
}
this.blockCommentBuffer.append(this.scanner.source, tokenStart, tokenLength);
bufferHasTokens = true;
bufferHasNewLine = false;
this.column += tokenLength;
multiLines = true;
hasTextOnFirstLine = -1;
} else {
// append token to the line
if (insertSpace) {
this.blockCommentTokensBuffer.append(' ');
}
this.blockCommentTokensBuffer.append(this.scanner.source, tokenStart, tokenLength);
}
previousToken = token;
newLine = false;
firstWord = false;
scannerLine = lineNumber;
lastTextLine = lineNumber;
}
// Replace block comment text
if (this.nlsTagCounter == 0 || !multiLines) {
if (hasTokens || multiLines) {
StringBuffer replacement;
if (hasTextOnFirstLine == 1) {
this.blockCommentTokensBuffer.setLength(0);
replacement = this.blockCommentTokensBuffer;
if ((hasMultiLines || multiLines)) {
int col = this.column;
replacement.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(replacement);
replacement.append(BLOCK_LINE_PREFIX);
this.column = col;
} else if (this.blockCommentBuffer.length() == 0 || this.blockCommentBuffer.charAt(0) != ' ') {
replacement.append(' ');
}
replacement.append(this.blockCommentBuffer);
} else {
replacement = this.blockCommentBuffer;
}
addReplaceEdit(editStart, editEnd, replacement.toString());
}
}
// Reset
this.indentationLevel = indentLevel;
this.numberOfIndentations = indentations;
this.lastNumberOfNewLines = 0;
this.needSpace = false;
this.scanner.resetTo(currentTokenEndPosition, this.scannerEndPosition - 1);
this.scanner.skipComments = false;
return true;
}
private void printBlockCommentHeaderLine(StringBuffer buffer) {
if (!this.formatter.preferences.comment_new_lines_at_block_boundaries) {
buffer.insert(0, ' ');
this.column++;
} else if (buffer.length() == 0) {
buffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(buffer);
buffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
} else {
this.tempBuffer.setLength(0);
this.tempBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
buffer.insert(0, this.tempBuffer.toString());
}
}
public void printEndOfCompilationUnit() {
try {
// if we have a space between two tokens we ensure it will be dumped in the formatted string
int currentTokenStartPosition = this.scanner.currentPosition;
boolean hasComment = false;
boolean hasLineComment = false;
boolean hasWhitespace = false;
int count = 0;
while (true) {
this.currentToken = this.scanner.getNextToken();
switch (this.currentToken) {
case TerminalTokens.TokenNameWHITESPACE:
char[] whiteSpaces = this.scanner.getCurrentTokenSource();
count = 0;
for (int i = 0, max = whiteSpaces.length; i < max; i++) {
switch (whiteSpaces[i]) {
case '\r':
if ((i + 1) < max) {
if (whiteSpaces[i + 1] == '\n') {
i++;
}
}
count++;
break;
case '\n':
count++;
}
}
if (count == 0) {
hasWhitespace = true;
addDeleteEdit(this.scanner.getCurrentTokenStartPosition(),
this.scanner.getCurrentTokenEndPosition());
} else if (hasLineComment) {
preserveEmptyLines(count, this.scanner.getCurrentTokenStartPosition());
addDeleteEdit(this.scanner.getCurrentTokenStartPosition(),
this.scanner.getCurrentTokenEndPosition());
} else if (hasComment) {
if (count == 1) {
this.printNewLine(this.scanner.getCurrentTokenStartPosition());
} else {
preserveEmptyLines(count - 1, this.scanner.getCurrentTokenStartPosition());
}
addDeleteEdit(this.scanner.getCurrentTokenStartPosition(),
this.scanner.getCurrentTokenEndPosition());
} else {
addDeleteEdit(this.scanner.getCurrentTokenStartPosition(),
this.scanner.getCurrentTokenEndPosition());
}
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameCOMMENT_LINE:
if (count >= 1) {
if (count > 1) {
preserveEmptyLines(count - 1, this.scanner.getCurrentTokenStartPosition());
} else if (count == 1) {
printNewLine(this.scanner.getCurrentTokenStartPosition());
}
} else if (hasWhitespace) {
space();
}
hasWhitespace = false;
printLineComment();
currentTokenStartPosition = this.scanner.currentPosition;
hasLineComment = true;
count = 0;
break;
case TerminalTokens.TokenNameCOMMENT_BLOCK:
if (count >= 1) {
if (count > 1) {
preserveEmptyLines(count - 1, this.scanner.getCurrentTokenStartPosition());
} else if (count == 1) {
printNewLine(this.scanner.getCurrentTokenStartPosition());
}
} else if (hasWhitespace) {
space();
}
hasWhitespace = false;
printBlockComment(false);
currentTokenStartPosition = this.scanner.currentPosition;
hasLineComment = false;
hasComment = true;
count = 0;
break;
case TerminalTokens.TokenNameCOMMENT_JAVADOC:
if (count >= 1) {
if (count > 1) {
preserveEmptyLines(count - 1, this.scanner.startPosition);
} else if (count == 1) {
printNewLine(this.scanner.startPosition);
}
} else if (hasWhitespace) {
space();
}
hasWhitespace = false;
if (includesJavadocComments()) {
printJavadocComment(this.scanner.startPosition, this.scanner.currentPosition);
} else {
printBlockComment(true);
}
printNewLine();
currentTokenStartPosition = this.scanner.currentPosition;
hasLineComment = false;
hasComment = true;
count = 0;
break;
case TerminalTokens.TokenNameSEMICOLON:
print(this.scanner.currentPosition - this.scanner.startPosition,
this.formatter.preferences.insert_space_before_semicolon);
break;
case TerminalTokens.TokenNameEOF:
if (count >= 1 || this.formatter.preferences.insert_new_line_at_end_of_file_if_missing) {
this.printNewLine(this.scannerEndPosition);
}
return;
default:
// step back one token
this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
return;
}
}
} catch (InvalidInputException e) {
throw new AbortFormatting(e);
}
}
/* prints a code snippet */
private void printCodeSnippet(int startPosition, int endPosition, int linesGap) {
// TODO
// String snippet = new String(this.scanner.source, startPosition, endPosition - startPosition + 1);
//
// // 1 - strip content prefix (@see JavaDocRegion#preprocessCodeSnippet)
// int firstLine = Util.getLineNumber(startPosition, this.lineEnds, 0, this.maxLines) - 1;
// int lastLine = Util.getLineNumber(endPosition, this.lineEnds, firstLine>1 ? firstLine-2 : 0, this.maxLines) - 1;
// this.codeSnippetBuffer.setLength(0);
// if (firstLine == lastLine && linesGap == 0) {
// this.codeSnippetBuffer.append(snippet);
// } else {
// boolean hasCharsAfterStar = false;
// if (linesGap == 0) {
// this.codeSnippetBuffer.append(this.scanner.source, startPosition, this.lineEnds[firstLine]+1-startPosition);
// firstLine++;
// }
// int initialLength = this.codeSnippetBuffer.length();
// for (int currentLine=firstLine; currentLine<=lastLine; currentLine++) {
// this.scanner.resetTo(this.lineEnds[currentLine-1]+1, this.lineEnds[currentLine]);
// int lineStart = this.scanner.currentPosition;
// boolean hasStar = false;
// loop: while (!this.scanner.atEnd()) {
// char ch = (char) this.scanner.getNextChar();
// switch (ch) {
// case ' ':
// case '\t' :
// case '\u000c' :
// break;
// case '\r' :
// case '\n' :
// break loop;
// case '*':
// hasStar = true;
// break loop;
// default:
// if (ScannerHelper.isWhitespace(ch)) {
// break;
// }
// break loop;
// }
// }
// if (hasStar) {
// lineStart = this.scanner.currentPosition;
// if (!hasCharsAfterStar && !this.scanner.atEnd()) {
// char ch = (char) this.scanner.getNextChar();
// boolean atEnd = this.scanner.atEnd();
// switch (ch) {
// case ' ':
// case '\t' :
// case '\u000c' :
// break;
// case '\r' :
// case '\n' :
// atEnd = true;
// break;
// default:
// if (!ScannerHelper.isWhitespace(ch)) {
// if (hasStar) {
// // A non whitespace character is just after the star
// // then we need to restart from the beginning without
// // consuming the space after the star
// hasCharsAfterStar = true;
// currentLine = firstLine-1;
// this.codeSnippetBuffer.setLength(initialLength);
// continue;
// }
// }
// break;
// }
// if (!hasCharsAfterStar && !atEnd) {
// // Until then, there's always a whitespace after each star
// // of the comment, hence we need to consume it as it will
// // be rewritten while reindenting the snippet lines
// lineStart = this.scanner.currentPosition;
// }
// }
// }
// int end = currentLine == lastLine ? endPosition : this.lineEnds[currentLine];
// this.codeSnippetBuffer.append(this.scanner.source, lineStart, end+1-lineStart);
// }
// }
//
// // 2 - convert HTML to Java (@see JavaDocRegion#convertHtml2Java)
// HTMLEntity2JavaReader reader= new HTMLEntity2JavaReader(new StringReader(this.codeSnippetBuffer.toString()));
// char[] buf= new char[this.codeSnippetBuffer.length()]; // html2text never gets longer, only shorter!
// String convertedSnippet;
// try {
// int read= reader.read(buf);
// convertedSnippet = new String(buf, 0, read);
// } catch (IOException e) {
// // should not happen
// CommentFormatterUtil.log(e);
// return;
// }
//
// // 3 - format snippet (@see JavaDocRegion#formatCodeSnippet)
// // include comments in case of line comments are present in the snippet
// String formattedSnippet = convertedSnippet;
// Map options = this.formatter.preferences.getMap();
// if (this.scanner.sourceLevel > ClassFileConstants.JDK1_3) {
// options.put(JavaCore.COMPILER_SOURCE, CompilerOptions.versionFromJdkLevel(this.scanner.sourceLevel));
// }
// TextEdit edit= CommentFormatterUtil.format2(CodeFormatter.K_UNKNOWN | CodeFormatter.F_INCLUDE_COMMENTS, convertedSnippet,
// 0, this.lineSeparator, options);
// if (edit == null) {
// // 3.a - not a valid code to format, keep initial buffer
// formattedSnippet = this.codeSnippetBuffer.toString();
// } else {
// // 3.b - valid code formatted
// // 3.b.i - get the result
// formattedSnippet = CommentFormatterUtil.evaluateFormatterEdit(convertedSnippet, edit, null);
//
// // 3.b.ii- convert back to HTML (@see JavaDocRegion#convertJava2Html)
// Java2HTMLEntityReader javaReader= new Java2HTMLEntityReader(new StringReader(formattedSnippet));
// buf= new char[256];
// this.codeSnippetBuffer.setLength(0);
// int l;
// try {
// do {
// l= javaReader.read(buf);
// if (l != -1)
// this.codeSnippetBuffer.append(buf, 0, l);
// } while (l > 0);
// formattedSnippet = this.codeSnippetBuffer.toString();
// } catch (IOException e) {
// // should not happen
// CommentFormatterUtil.log(e);
// return;
// }
// }
//
// // 4 - add the content prefix (@see JavaDocRegion#postprocessCodeSnippet)
// this.codeSnippetBuffer.setLength(0);
// ILineTracker tracker = new DefaultLineTracker();
// this.column = 1;
// printIndentationIfNecessary(this.codeSnippetBuffer); // append indentation
// this.codeSnippetBuffer.append(BLOCK_LINE_PREFIX);
// String linePrefix = this.codeSnippetBuffer.toString();
// this.codeSnippetBuffer.setLength(0);
// String replacement = formattedSnippet;
// tracker.set(formattedSnippet);
// int numberOfLines = tracker.getNumberOfLines();
// if (numberOfLines > 1) {
// int lastLineOffset = -1;
// for (int i=0; i<numberOfLines-1; i++) {
// if (i>0) this.codeSnippetBuffer.append(linePrefix);
// try {
// lastLineOffset = tracker.getLineOffset(i+1);
// this.codeSnippetBuffer.append(formattedSnippet.substring(tracker.getLineOffset(i), lastLineOffset));
// } catch (BadLocationException e) {
// // should not happen
// CommentFormatterUtil.log(e);
// return;
// }
// }
// this.codeSnippetBuffer.append(linePrefix);
// this.codeSnippetBuffer.append(formattedSnippet.substring(lastLineOffset));
// replacement = this.codeSnippetBuffer.toString();
// }
//
// // 5 - replace old text with the formatted snippet
// addReplaceEdit(startPosition, endPosition, replacement);
}
void printComment() {
printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT, PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION);
}
void printComment(int emptyLinesRules) {
printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT, emptyLinesRules);
}
void printComment(int kind, int trailing) {
printComment(kind, trailing, PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION);
}
/*
* Main method to print and format comments (javadoc, block and single line comments)
*/
void printComment(int kind, int trailing, int emptyLinesRules) {
final boolean rejectLineComment = kind == CodeFormatter.K_MULTI_LINE_COMMENT || kind == CodeFormatter.K_JAVA_DOC;
final boolean rejectBlockComment =
kind == CodeFormatter.K_SINGLE_LINE_COMMENT || kind == CodeFormatter.K_JAVA_DOC;
final boolean rejectJavadocComment =
kind == CodeFormatter.K_SINGLE_LINE_COMMENT || kind == CodeFormatter.K_MULTI_LINE_COMMENT;
try {
// if we have a space between two tokens we ensure it will be dumped in the formatted string
int currentTokenStartPosition = this.scanner.currentPosition;
boolean hasComment = false;
boolean hasLineComment = false;
boolean hasWhitespaces = false;
int lines = 0;
while ((this.currentToken = this.scanner.getNextToken()) != TerminalTokens.TokenNameEOF) {
int foundTaskCount = this.scanner.foundTaskCount;
int tokenStartPosition = this.scanner.getCurrentTokenStartPosition();
switch (this.currentToken) {
case TerminalTokens.TokenNameWHITESPACE:
char[] whiteSpaces = this.scanner.getCurrentTokenSource();
int whitespacesEndPosition = this.scanner.getCurrentTokenEndPosition();
lines = 0;
for (int i = 0, max = whiteSpaces.length; i < max; i++) {
switch (whiteSpaces[i]) {
case '\r':
if ((i + 1) < max) {
if (whiteSpaces[i + 1] == '\n') {
i++;
}
}
lines++;
break;
case '\n':
lines++;
}
}
// If following token is a line comment on the same line or the line just after,
// then it might be not really formatted as a trailing comment
boolean realTrailing = trailing > NO_TRAILING_COMMENT;
if (realTrailing && this.scanner.currentCharacter == '/'
&& (lines == 0 || (lines == 1 && !hasLineComment && trailing == IMPORT_TRAILING_COMMENT))) {
// sometimes changing the trailing may not be the best idea
// for complex trailing comment, it's basically a good idea
boolean canChangeTrailing = (trailing & COMPLEX_TRAILING_COMMENT) != 0;
// for basic trailing comment preceded by a line comment, then it depends on the comments relative position
// when following comment column (after having been rounded) is below the preceding one,
// then it becomes not a good idea to change the trailing flag
if (trailing == BASIC_TRAILING_COMMENT && hasLineComment) {
int currentCommentIndentation = getCurrentIndentation(whiteSpaces, 0);
int relativeIndentation = currentCommentIndentation - this.lastLineComment.currentIndentation;
if (this.tabLength == 0) {
canChangeTrailing = relativeIndentation == 0;
} else {
canChangeTrailing = relativeIndentation > -this.tabLength;
}
}
// if the trailing can be change, then look at the following tokens
if (canChangeTrailing) {
int currentPosition = this.scanner.currentPosition;
if (this.scanner.getNextToken() == TerminalTokens.TokenNameCOMMENT_LINE) {
realTrailing = !hasLineComment;
switch (this.scanner.getNextToken()) {
case TerminalTokens.TokenNameCOMMENT_LINE:
// at least two contiguous line comments
// the formatter should not consider comments as trailing ones
realTrailing = false;
break;
case TerminalTokens.TokenNameWHITESPACE:
if (this.scanner.getNextToken() == TerminalTokens.TokenNameCOMMENT_LINE) {
// at least two contiguous line comments
// the formatter should not consider comments as trailing ones
realTrailing = false;
}
break;
}
}
this.scanner.resetTo(currentPosition, this.scanner.eofPosition - 1);
}
}
// Look whether comments line may be contiguous or not
// Note that when preceding token is a comment line, then only one line
// is enough to have an empty line as the line end is included in the comment line...
// If comments are contiguous, store the white spaces to be able to compute the current comment indentation
if (lines > 1 || (lines == 1 && hasLineComment)) {
this.lastLineComment.contiguous = false;
}
this.lastLineComment.leadingSpaces = whiteSpaces;
this.lastLineComment.lines = lines;
// Strategy to consume spaces and eventually leave at this stage
// depends on the fact that a trailing comment is expected or not
if (realTrailing) {
// if a line comment is consumed, no other comment can be on the same line after
if (hasLineComment) {
if (lines >= 1) {
currentTokenStartPosition = tokenStartPosition;
preserveEmptyLines(lines, currentTokenStartPosition);
addDeleteEdit(currentTokenStartPosition, whitespacesEndPosition);
this.scanner.resetTo(this.scanner.currentPosition, this.scannerEndPosition - 1);
return;
}
this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
return;
}
// if one or several new lines are consumed, following comments cannot be considered as trailing ones
if (lines >= 1) {
if (hasComment) {
this.printNewLine(tokenStartPosition);
}
this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
return;
}
// delete consumed white spaces
hasWhitespaces = true;
currentTokenStartPosition = this.scanner.currentPosition;
addDeleteEdit(tokenStartPosition, whitespacesEndPosition);
} else {
if (lines == 0) {
hasWhitespaces = true;
if (hasLineComment && emptyLinesRules != PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION) {
addReplaceEdit(tokenStartPosition, whitespacesEndPosition,
getPreserveEmptyLines(0, emptyLinesRules));
} else {
addDeleteEdit(tokenStartPosition, whitespacesEndPosition);
}
} else if (hasLineComment) {
useAlignmentBreakIndentation(emptyLinesRules);
currentTokenStartPosition = tokenStartPosition;
preserveEmptyLines(lines, currentTokenStartPosition);
addDeleteEdit(currentTokenStartPosition, whitespacesEndPosition);
} else if (hasComment) {
useAlignmentBreakIndentation(emptyLinesRules);
if (lines == 1) {
this.printNewLine(tokenStartPosition);
} else {
preserveEmptyLines(lines - 1, tokenStartPosition);
}
addDeleteEdit(tokenStartPosition, whitespacesEndPosition);
} else if (lines != 0
&& (!this.formatter.preferences.join_wrapped_lines
|| this.formatter.preferences.number_of_empty_lines_to_preserve != 0 ||
this.blank_lines_between_import_groups > 0)) {
addReplaceEdit(tokenStartPosition, whitespacesEndPosition,
getPreserveEmptyLines(lines - 1, emptyLinesRules));
} else {
useAlignmentBreakIndentation(emptyLinesRules);
addDeleteEdit(tokenStartPosition, whitespacesEndPosition);
}
}
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameCOMMENT_LINE:
if (this.useTags && this.editsEnabled) {
boolean turnOff = false;
if (foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
turnOff = true;
} else if (this.tagsKind == this.currentToken
&& CharOperation.fragmentEquals(this.disablingTag, this.scanner.source, tokenStartPosition,
true)) {
this.editsEnabled = false;
turnOff = true;
}
if (turnOff) {
if (!this.editsEnabled && this.editsIndex > 1) {
OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex - 1];
if (this.scanner.startPosition == currentEdit.offset + currentEdit.length) {
printNewLinesBeforeDisablingComment();
}
}
}
}
if (rejectLineComment)
break;
if (lines >= 1) {
if (lines > 1) {
preserveEmptyLines(lines - 1, this.scanner.getCurrentTokenStartPosition());
} else if (lines == 1) {
printNewLine(this.scanner.getCurrentTokenStartPosition());
}
} else if (hasWhitespaces) {
space();
}
hasWhitespaces = false;
printLineComment();
currentTokenStartPosition = this.scanner.currentPosition;
hasLineComment = true;
lines = 0;
if (this.useTags && !this.editsEnabled) {
if (foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
} else if (this.tagsKind == this.currentToken) {
this.editsEnabled =
CharOperation
.fragmentEquals(this.enablingTag, this.scanner.source, tokenStartPosition, true);
}
}
break;
case TerminalTokens.TokenNameCOMMENT_BLOCK:
if (this.useTags && this.editsEnabled) {
boolean turnOff = false;
if (foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
turnOff = true;
} else if (this.tagsKind == this.currentToken
&& CharOperation.fragmentEquals(this.disablingTag, this.scanner.source, tokenStartPosition,
true)) {
this.editsEnabled = false;
turnOff = true;
}
if (turnOff) {
if (!this.editsEnabled && this.editsIndex > 1) {
OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex - 1];
if (this.scanner.startPosition == currentEdit.offset + currentEdit.length) {
printNewLinesBeforeDisablingComment();
}
}
}
}
if (trailing > NO_TRAILING_COMMENT && lines >= 1) {
// a block comment on next line means that there's no trailing comment
this.scanner.resetTo(this.scanner.getCurrentTokenStartPosition(), this.scannerEndPosition - 1);
return;
}
this.lastLineComment.contiguous = false;
if (rejectBlockComment)
break;
if (lines >= 1) {
if (lines > 1) {
preserveEmptyLines(lines - 1, this.scanner.getCurrentTokenStartPosition());
} else if (lines == 1) {
printNewLine(this.scanner.getCurrentTokenStartPosition());
}
} else if (hasWhitespaces) {
space();
}
hasWhitespaces = false;
printBlockComment(false);
currentTokenStartPosition = this.scanner.currentPosition;
hasLineComment = false;
hasComment = true;
lines = 0;
if (this.useTags && !this.editsEnabled) {
if (foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
} else if (this.tagsKind == this.currentToken) {
this.editsEnabled =
CharOperation
.fragmentEquals(this.enablingTag, this.scanner.source, tokenStartPosition, true);
}
}
break;
case TerminalTokens.TokenNameCOMMENT_JAVADOC:
if (this.useTags && this.editsEnabled && foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
if (!this.editsEnabled && this.editsIndex > 1) {
OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex - 1];
if (this.scanner.startPosition == currentEdit.offset + currentEdit.length) {
printNewLinesBeforeDisablingComment();
}
}
}
if (trailing > NO_TRAILING_COMMENT) {
// a javadoc comment should not be considered as a trailing comment
this.scanner.resetTo(this.scanner.getCurrentTokenStartPosition(), this.scannerEndPosition - 1);
return;
}
this.lastLineComment.contiguous = false;
if (rejectJavadocComment)
break;
if (lines >= 1) {
if (lines > 1) {
preserveEmptyLines(lines - 1, this.scanner.getCurrentTokenStartPosition());
} else if (lines == 1) {
printNewLine(this.scanner.getCurrentTokenStartPosition());
}
} else if (hasWhitespaces) {
space();
}
hasWhitespaces = false;
if (includesJavadocComments()) {
printJavadocComment(this.scanner.startPosition, this.scanner.currentPosition);
} else {
printBlockComment(true);
}
if (this.useTags && !this.editsEnabled && foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
}
printNewLine();
currentTokenStartPosition = this.scanner.currentPosition;
hasLineComment = false;
hasComment = true;
lines = 0;
break;
default:
this.lastLineComment.contiguous = false;
// step back one token
this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
return;
}
}
} catch (InvalidInputException e) {
throw new AbortFormatting(e);
}
}
void printComment(int kind, String source, int start, int end, int level) {
// Set scanner
resetScanner(source.toCharArray());
this.scanner.resetTo(start, end);
// Put back 3.4RC2 code => comment following line as it has an impact on Linux tests
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=234336
// TODO (frederic) Need more investigations and a better fix in
// isAdaptableRegion(int) and adaptRegions()
// this.scannerEndPosition = end;
// Set indentation level
this.numberOfIndentations = level;
this.indentationLevel = level * this.indentationSize;
this.column = this.indentationLevel + 1;
// Print corresponding comment
switch (kind) {
case CodeFormatter.K_SINGLE_LINE_COMMENT:
printComment(kind, NO_TRAILING_COMMENT);
break;
case CodeFormatter.K_MULTI_LINE_COMMENT:
printComment(kind, NO_TRAILING_COMMENT);
break;
case CodeFormatter.K_JAVA_DOC:
printJavadocComment(start, end);
break;
}
}
private void printLineComment() {
int currentTokenStartPosition = this.scanner.getCurrentTokenStartPosition();
int currentTokenEndPosition = this.scanner.getCurrentTokenEndPosition() + 1;
boolean includesLineComments = includesLineComments();
boolean isNlsTag = false;
if (CharOperation.indexOf(Scanner.TAG_PREFIX, this.scanner.source, true, currentTokenStartPosition,
currentTokenEndPosition) != -1) {
this.nlsTagCounter = 0;
isNlsTag = true;
}
this.scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition - 1);
int currentCharacter;
int start = currentTokenStartPosition;
int nextCharacterStart = currentTokenStartPosition;
// Print comment line indentation
int commentIndentationLevel;
boolean onFirstColumn = isOnFirstColumn(start);
if (this.indentationLevel == 0) {
commentIndentationLevel = this.column - 1;
} else {
if (onFirstColumn
&& ((includesLineComments && !this.formatter.preferences.comment_format_line_comment_starting_on_first_column) ||
this.formatter.preferences.never_indent_line_comments_on_first_column)) {
commentIndentationLevel = this.column - 1;
} else {
// Indentation may be specific for contiguous comment
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=293300
if (this.lastLineComment.contiguous) {
// The leading spaces have been set while looping in the printComment(int) method
int currentCommentIndentation = getCurrentIndentation(this.lastLineComment.leadingSpaces, 0);
// Keep the current comment indentation when over the previous contiguous line comment
// and the previous comment has not been reindented
int relativeIndentation = currentCommentIndentation - this.lastLineComment.currentIndentation;
boolean similarCommentsIndentation = false;
if (this.tabLength == 0) {
similarCommentsIndentation = relativeIndentation == 0;
} else if (relativeIndentation > -this.tabLength) {
similarCommentsIndentation =
relativeIndentation == 0 || currentCommentIndentation != 0
&& this.lastLineComment.currentIndentation != 0;
}
if (similarCommentsIndentation && this.lastLineComment.indentation != this.indentationLevel) {
int currentIndentationLevel = this.indentationLevel;
this.indentationLevel = this.lastLineComment.indentation;
printIndentationIfNecessary();
this.indentationLevel = currentIndentationLevel;
commentIndentationLevel = this.lastLineComment.indentation;
} else {
printIndentationIfNecessary();
commentIndentationLevel = this.column - 1;
}
} else {
if (this.currentAlignment != null && this.currentAlignment.kind == Alignment.ARRAY_INITIALIZER
&& this.currentAlignment.fragmentCount > 0
&& this.indentationLevel < this.currentAlignment.breakIndentationLevel
&& this.lastLineComment.lines > 0) {
int currentIndentationLevel = this.indentationLevel;
this.indentationLevel = this.currentAlignment.breakIndentationLevel;
printIndentationIfNecessary();
this.indentationLevel = currentIndentationLevel;
commentIndentationLevel = this.currentAlignment.breakIndentationLevel;
} else {
printIndentationIfNecessary();
commentIndentationLevel = this.column - 1;
}
}
}
}
// Store line comment information
this.lastLineComment.contiguous = true;
this.lastLineComment.currentIndentation = getCurrentCommentIndentation(currentTokenStartPosition);
this.lastLineComment.indentation = commentIndentationLevel;
// Add pending space if necessary
if (this.pendingSpace) {
if (this.formatter.preferences.comment_preserve_white_space_between_code_and_line_comments) {
addInsertEdit(currentTokenStartPosition, new String(this.lastLineComment.leadingSpaces));
} else {
addInsertEdit(currentTokenStartPosition, " "); //$NON-NLS-1$
}
}
this.needSpace = false;
this.pendingSpace = false;
int previousStart = currentTokenStartPosition;
if (!isNlsTag && includesLineComments
&& (!onFirstColumn || this.formatter.preferences.comment_format_line_comment_starting_on_first_column)) {
printLineComment(currentTokenStartPosition, currentTokenEndPosition - 1);
} else {
// do nothing!?
loop:
while (nextCharacterStart <= currentTokenEndPosition
&& (currentCharacter = this.scanner.getNextChar()) != -1) {
nextCharacterStart = this.scanner.currentPosition;
switch (currentCharacter) {
case '\r':
start = previousStart;
break loop;
case '\n':
start = previousStart;
break loop;
}
previousStart = nextCharacterStart;
}
if (start != currentTokenStartPosition) {
// this means that the line comment doesn't end the file
addReplaceEdit(start, currentTokenEndPosition - 1, this.lineSeparator);
this.line++;
this.column = 1;
this.lastNumberOfNewLines = 1;
}
}
this.needSpace = false;
this.pendingSpace = false;
// realign to the proper value
if (this.currentAlignment != null) {
if (this.memberAlignment != null) {
// select the last alignment
if (this.currentAlignment.location.inputOffset > this.memberAlignment.location.inputOffset) {
if (this.currentAlignment.couldBreak() && this.currentAlignment.wasSplit) {
this.currentAlignment.performFragmentEffect();
}
} else {
this.indentationLevel = Math.max(this.indentationLevel, this.memberAlignment.breakIndentationLevel);
}
} else if (this.currentAlignment.couldBreak() && this.currentAlignment.wasSplit) {
this.currentAlignment.performFragmentEffect();
}
if (this.currentAlignment.kind == Alignment.BINARY_EXPRESSION && this.currentAlignment.enclosing != null
&& this.currentAlignment.enclosing.kind == Alignment.BINARY_EXPRESSION
&& this.indentationLevel < this.currentAlignment.breakIndentationLevel) {
this.indentationLevel = this.currentAlignment.breakIndentationLevel;
}
}
this.scanner.resetTo(currentTokenEndPosition, this.scannerEndPosition - 1);
}
private void printLineComment(int commentStart, int commentEnd) {
// Compute indentation
int firstColumn = this.column;
int indentLevel = this.indentationLevel;
int indentations = this.numberOfIndentations;
this.indentationLevel = getNextIndentationLevel(firstColumn);
if (this.indentationSize != 0) {
this.numberOfIndentations = this.indentationLevel / this.indentationSize;
} else {
this.numberOfIndentations = 0;
}
// Consume the comment prefix
this.scanner.resetTo(commentStart, commentEnd);
this.scanner.getNextChar();
this.scanner.getNextChar();
this.column += 2;
// Scan the text token per token to compact it and size it the max line length
int maxColumn = this.formatter.preferences.comment_line_length + 1;
int previousToken = -1;
int lastTokenEndPosition = commentStart;
int spaceStartPosition = -1;
int spaceEndPosition = -1;
this.scanner.skipComments = true;
String newLineString = null;
this.commentIndentation = null;
// Consume text token per token
while (!this.scanner.atEnd()) {
int token;
try {
token = this.scanner.getNextToken();
} catch (InvalidInputException iie) {
token = consumeInvalidToken(commentEnd);
}
switch (token) {
case TerminalTokens.TokenNameWHITESPACE:
if (previousToken == -1) {
// do not remember the first whitespace
previousToken = SKIP_FIRST_WHITESPACE_TOKEN;
} else {
previousToken = token;
}
// Remember space position
spaceStartPosition = this.scanner.getCurrentTokenStartPosition();
spaceEndPosition = this.scanner.getCurrentTokenEndPosition();
continue;
case TerminalTokens.TokenNameEOF:
continue;
case TerminalTokens.TokenNameIdentifier:
if (previousToken == -1 || previousToken == SKIP_FIRST_WHITESPACE_TOKEN) {
char[] identifier = this.scanner.getCurrentTokenSource();
int startPosition = this.scanner.getCurrentTokenStartPosition();
int restartPosition = this.scanner.currentPosition;
if (CharOperation.equals(identifier, Parser.FALL_THROUGH_TAG, 0, 5/*
* length of string "$FALL"
*/)
&& this.scanner.currentCharacter == '-') {
try {
this.scanner.getNextToken(); // consume the '-'
token = this.scanner.getNextToken(); // consume the "THROUGH"
if (token == TerminalTokens.TokenNameIdentifier) {
identifier = this.scanner.getCurrentTokenSource();
if (CharOperation.endsWith(Parser.FALL_THROUGH_TAG, identifier)) {
// the comment starts with a fall through
if (previousToken == SKIP_FIRST_WHITESPACE_TOKEN) {
addReplaceEdit(spaceStartPosition, startPosition - 1, " "); //$NON-NLS-1$
}
this.scanner.startPosition = startPosition;
previousToken = token;
break;
}
}
} catch (InvalidInputException iie) {
// skip
}
}
// this was not a valid fall-through tag, hence continue to process the comment normally
this.scanner.startPosition = startPosition;
this.scanner.currentPosition = restartPosition;
}
break;
}
int tokenStart = this.scanner.getCurrentTokenStartPosition();
int tokenLength =
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - tokenStart;
// insert space at the beginning if not present
if (previousToken == -1) {
addInsertEdit(this.scanner.startPosition, " "); //$NON-NLS-1$
this.column++;
}
// replace space at the beginning if present
else if (previousToken == SKIP_FIRST_WHITESPACE_TOKEN) {
addReplaceEdit(spaceStartPosition, this.scanner.startPosition - 1, " "); //$NON-NLS-1$
this.column++;
spaceStartPosition = -1; // do not use this position to split the comment
} else {
// not on the first token
boolean insertSpace = previousToken == TerminalTokens.TokenNameWHITESPACE;
if (insertSpace) {
// count inserted space if any in token length
tokenLength++;
}
// insert new line if max line width is reached and a space was previously encountered
if (spaceStartPosition > 0 && (this.column + tokenLength) > maxColumn) {
this.lastNumberOfNewLines++;
this.line++;
if (newLineString == null) {
this.tempBuffer.setLength(0);
this.tempBuffer.append(this.lineSeparator);
this.column = 1;
if (!this.formatter.preferences.never_indent_line_comments_on_first_column) {
printIndentationIfNecessary(this.tempBuffer);
}
this.tempBuffer.append(LINE_COMMENT_PREFIX);
this.column += LINE_COMMENT_PREFIX_LENGTH;
newLineString = this.tempBuffer.toString();
firstColumn = this.column;
} else {
this.column = firstColumn;
}
if (lastTokenEndPosition > spaceEndPosition) {
this.column += lastTokenEndPosition - (spaceEndPosition + 1); // add all previous tokens lengths since last
// space
}
if (this.edits[this.editsIndex - 1].offset == spaceStartPosition) {
// previous space was already edited, so remove it
this.editsIndex--;
}
addReplaceEdit(spaceStartPosition, spaceEndPosition, newLineString);
spaceStartPosition = -1;
if (insertSpace) {
tokenLength--; // reduce token length as the space will be replaced by the new line
}
}
// replace space if needed
else if (insertSpace) {
addReplaceEdit(spaceStartPosition, this.scanner.startPosition - 1, " "); //$NON-NLS-1$
}
}
// update column position and store info of the current token
this.column += tokenLength;
previousToken = token;
lastTokenEndPosition = this.scanner.currentPosition;
}
this.scanner.skipComments = false;
// Skip separator if the comment is not at the end of file
this.indentationLevel = indentLevel;
this.numberOfIndentations = indentations;
this.lastNumberOfNewLines = 0;
this.scanner.resetTo(lastTokenEndPosition, commentEnd);
while (!this.scanner.atEnd()) {
spaceEndPosition = this.scanner.currentPosition;
this.scanner.getNextChar();
if (this.scanner.currentCharacter == '\n' || this.scanner.currentCharacter == '\r') {
// line comment is normally ended with new line
this.column = 1;
this.line++;
this.lastNumberOfNewLines++;
break;
}
}
// Replace the line separator at the end of the comment if any...
int startReplace = previousToken == SKIP_FIRST_WHITESPACE_TOKEN ? spaceStartPosition : lastTokenEndPosition;
if (this.column == 1 && commentEnd >= startReplace) {
addReplaceEdit(startReplace, commentEnd, this.formatter.preferences.line_separator);
}
}
public void printEmptyLines(int linesNumber) {
this.printEmptyLines(linesNumber, this.scanner.getCurrentTokenEndPosition() + 1);
}
private void printEmptyLines(int linesNumber, int insertPosition) {
final String buffer = getEmptyLines(linesNumber);
if (Util.EMPTY_STRING == buffer)
return;
addInsertEdit(insertPosition, buffer);
}
void printIndentationIfNecessary() {
this.tempBuffer.setLength(0);
printIndentationIfNecessary(this.tempBuffer);
if (this.tempBuffer.length() > 0) {
addInsertEdit(this.scanner.getCurrentTokenStartPosition(), this.tempBuffer.toString());
this.pendingSpace = false;
}
}
private void printIndentationIfNecessary(StringBuffer buffer) {
switch (this.tabChar) {
case DefaultCodeFormatterOptions.TAB:
boolean useTabsForLeadingIndents = this.useTabsOnlyForLeadingIndents;
int numberOfLeadingIndents = this.numberOfIndentations;
int indentationsAsTab = 0;
if (useTabsForLeadingIndents) {
while (this.column <= this.indentationLevel) {
if (this.tabLength > 0 && indentationsAsTab < numberOfLeadingIndents) {
if (buffer != null)
buffer.append('\t');
indentationsAsTab++;
int complement = this.tabLength - ((this.column - 1) % this.tabLength); // amount of space
this.column += complement;
} else {
if (buffer != null)
buffer.append(' ');
this.column++;
}
this.needSpace = false;
}
} else if (this.tabLength > 0) {
while (this.column <= this.indentationLevel) {
if (buffer != null)
buffer.append('\t');
int complement = this.tabLength - ((this.column - 1) % this.tabLength); // amount of space
this.column += complement;
this.needSpace = false;
}
}
break;
case DefaultCodeFormatterOptions.SPACE:
while (this.column <= this.indentationLevel) {
if (buffer != null)
buffer.append(' ');
this.column++;
this.needSpace = false;
}
break;
case DefaultCodeFormatterOptions.MIXED:
useTabsForLeadingIndents = this.useTabsOnlyForLeadingIndents;
numberOfLeadingIndents = this.numberOfIndentations;
indentationsAsTab = 0;
if (useTabsForLeadingIndents) {
final int columnForLeadingIndents = numberOfLeadingIndents * this.indentationSize;
while (this.column <= this.indentationLevel) {
if (this.column <= columnForLeadingIndents) {
if (this.tabLength > 0 && (this.column - 1 + this.tabLength) <= this.indentationLevel) {
if (buffer != null)
buffer.append('\t');
this.column += this.tabLength;
} else if ((this.column - 1 + this.indentationSize) <= this.indentationLevel) {
// print one indentation
// note that this.indentationSize > 0 when entering in the following loop
// hence this.column will be incremented and then avoid endless loop (see bug 290905)
for (int i = 0, max = this.indentationSize; i < max; i++) {
if (buffer != null)
buffer.append(' ');
this.column++;
}
} else {
if (buffer != null)
buffer.append(' ');
this.column++;
}
} else {
for (int i = this.column, max = this.indentationLevel; i <= max; i++) {
if (buffer != null)
buffer.append(' ');
this.column++;
}
}
this.needSpace = false;
}
} else {
while (this.column <= this.indentationLevel) {
if (this.tabLength > 0 && (this.column - 1 + this.tabLength) <= this.indentationLevel) {
if (buffer != null)
buffer.append('\t');
this.column += this.tabLength;
} else if (this.indentationSize > 0
&& (this.column - 1 + this.indentationSize) <= this.indentationLevel) {
// print one indentation
for (int i = 0, max = this.indentationSize; i < max; i++) {
if (buffer != null)
buffer.append(' ');
this.column++;
}
} else {
if (buffer != null)
buffer.append(' ');
this.column++;
}
this.needSpace = false;
}
}
break;
}
}
private void printJavadocBlock(FormatJavadocBlock block) {
if (block == null)
return;
// Init positions
int previousEnd = block.tagEnd;
int maxNodes = block.nodesPtr;
// Compute indentation
boolean headerLine = block.isHeaderLine() && this.lastNumberOfNewLines == 0;
int maxColumn = this.formatter.preferences.comment_line_length + 1;
if (headerLine) {
maxColumn++;
}
// format tag section if necessary
if (!block.isInlined()) {
this.lastNumberOfNewLines = 0;
}
if (block.isDescription()) {
if (!block.isInlined()) {
this.commentIndentation = null;
}
} else {
int tagLength = previousEnd - block.sourceStart + 1;
this.column += tagLength;
if (!block.isInlined()) {
boolean indentRootTags = this.formatter.preferences.comment_indent_root_tags && !block.isInDescription();
int commentIndentationLevel = 0;
if (indentRootTags) {
commentIndentationLevel = tagLength + 1;
boolean indentParamTag =
this.formatter.preferences.comment_indent_parameter_description && block.isInParamTag();
if (indentParamTag) {
commentIndentationLevel += this.indentationSize;
}
}
setCommentIndentation(commentIndentationLevel);
}
FormatJavadocReference reference = block.reference;
if (reference != null) {
// format reference
printJavadocBlockReference(block, reference);
previousEnd = reference.sourceEnd;
}
// Nothing else to do if the tag has no node
if (maxNodes < 0) {
if (block.isInlined()) {
this.column++;
}
return;
}
}
// tag section: iterate through the blocks composing this tag but the last one
int previousLine = Util.getLineNumber(previousEnd, this.lineEnds, 0, this.maxLines);
boolean clearBlankLines = this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment;
boolean joinLines = this.formatter.preferences.join_lines_in_comments;
for (int i = 0; i <= maxNodes; i++) {
FormatJavadocNode node = block.nodes[i];
int nodeStart = node.sourceStart;
// Print empty lines before the node
int newLines;
if (i == 0) {
newLines = this.formatter.preferences.comment_insert_new_line_for_parameter && block.isParamTag() ? 1 : 0;
if (nodeStart > (previousEnd + 1)) {
if (!clearBlankLines || !joinLines) {
int startLine = Util.getLineNumber(nodeStart, this.lineEnds, previousLine - 1, this.maxLines);
int gapLine = previousLine;
if (joinLines)
gapLine++; // if not preserving line break then gap must be at least of one line
if (startLine > gapLine) {
newLines = startLine - previousLine;
}
if (clearBlankLines) {
// clearing blank lines in this block means that break lines should be preserved, hence only keep one new
// line
if (newLines > 0)
newLines = 1;
}
}
if (newLines == 0 && (!node.isImmutable() || block.reference != null)) {
newLines = printJavadocBlockNodesNewLines(block, node, previousEnd);
}
if (block.isImmutable()) {
printJavadocGapLinesForImmutableBlock(block);
} else {
printJavadocGapLines(previousEnd + 1, nodeStart - 1, newLines, clearBlankLines, false, null);
}
} else {
this.tempBuffer.setLength(0);
if (newLines > 0) {
for (int j = 0; j < newLines; j++) {
printJavadocNewLine(this.tempBuffer);
}
addInsertEdit(nodeStart, this.tempBuffer.toString());
}
}
} else {
newLines = this.column > maxColumn ? 1 : 0;
if (!clearBlankLines && node.lineStart > (previousLine + 1))
newLines = node.lineStart - previousLine;
if (newLines < node.linesBefore)
newLines = node.linesBefore;
if (newLines == 0) {
newLines = printJavadocBlockNodesNewLines(block, node, previousEnd);
}
if (newLines > 0 || nodeStart > (previousEnd + 1)) {
printJavadocGapLines(previousEnd + 1, nodeStart - 1, newLines, clearBlankLines, false, null);
}
}
if (headerLine && newLines > 0) {
headerLine = false;
maxColumn--;
}
// Print node
if (node.isText()) {
FormatJavadocText text = (FormatJavadocText)node;
if (text.isImmutable()) {
// Indent if new line was added
if (text.isImmutableHtmlTag() && newLines > 0 && this.commentIndentation != null) {
addInsertEdit(node.sourceStart, this.commentIndentation);
this.column += this.commentIndentation.length();
}
printJavadocImmutableText(text, block, newLines > 0);
this.column += getTextLength(block, text);
} else if (text.isHtmlTag()) {
printJavadocHtmlTag(text, block, newLines > 0);
} else {
printJavadocText(text, block, newLines > 0);
}
} else {
if (newLines > 0 && this.commentIndentation != null) {
addInsertEdit(node.sourceStart, this.commentIndentation);
this.column += this.commentIndentation.length();
}
printJavadocBlock((FormatJavadocBlock)node);
}
// Print empty lines before the node
previousEnd = node.sourceEnd;
previousLine =
Util.getLineNumber(previousEnd, this.lineEnds, node.lineStart > 1 ? node.lineStart - 2 : 0, this.maxLines);
}
this.lastNumberOfNewLines = 0;
}
private int printJavadocBlockNodesNewLines(FormatJavadocBlock block, FormatJavadocNode node, int previousEnd) {
int maxColumn = this.formatter.preferences.comment_line_length + 1;
int nodeStart = node.sourceStart;
try {
this.scanner.resetTo(nodeStart, node.sourceEnd);
int length = 0;
boolean newLine = false;
boolean headerLine = block.isHeaderLine() && this.lastNumberOfNewLines == 0;
int firstColumn = 1 + this.indentationLevel + BLOCK_LINE_PREFIX_LENGTH;
if (this.commentIndentation != null)
firstColumn += this.commentIndentation.length();
if (headerLine)
maxColumn++;
FormatJavadocText text = null;
boolean isImmutableNode = node.isImmutable();
boolean nodeIsText = node.isText();
if (nodeIsText) {
text = (FormatJavadocText)node;
} else {
FormatJavadocBlock inlinedBlock = (FormatJavadocBlock)node;
if (isImmutableNode) {
text = (FormatJavadocText)inlinedBlock.getLastNode();
if (text != null) {
length += inlinedBlock.tagEnd - inlinedBlock.sourceStart + 1; // tag length
if (nodeStart > (previousEnd + 1)) {
length++; // include space between nodes
}
this.scanner.resetTo(text.sourceStart, node.sourceEnd);
}
}
}
if (text != null) {
if (isImmutableNode) {
if (nodeStart > (previousEnd + 1)) {
length++; // include space between nodes
}
int lastColumn = this.column + length;
while (!this.scanner.atEnd()) {
try {
int token = this.scanner.getNextToken();
switch (token) {
case TerminalTokens.TokenNameWHITESPACE:
if (CharOperation.indexOf('\n', this.scanner.source, this.scanner.startPosition,
this.scanner.currentPosition) >= 0) {
return 0;
}
lastColumn = getCurrentIndentation(this.scanner.getCurrentTokenSource(), lastColumn);
break;
case TerminalTokens.TokenNameMULTIPLY:
if (newLine) {
newLine = false;
continue;
}
lastColumn++;
break;
default:
lastColumn +=
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition)
- this.scanner.startPosition;
break;
}
} catch (InvalidInputException iie) {
// maybe an unterminated string or comment
lastColumn +=
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition)
- this.scanner.startPosition;
}
if (lastColumn > maxColumn) {
return 1;
}
}
return 0;
}
if (text.isHtmlTag()) {
if (text.getHtmlTagID() == JAVADOC_SINGLE_BREAK_TAG_ID) {
// never break before single break tag
return 0;
}
// read the html tag
this.scanner.getNextToken();
if (this.scanner.getNextToken() == TerminalTokens.TokenNameDIVIDE) {
length++;
this.scanner.getNextToken();
}
length +=
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition)
- this.scanner.startPosition;
this.scanner.getNextToken(); // '>'
length++;
} else {
while (true) {
int token = this.scanner.getNextToken();
if (token == TerminalTokens.TokenNameWHITESPACE || token == TerminalTokens.TokenNameEOF)
break;
int tokenLength =
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition)
- this.scanner.startPosition;
length += tokenLength;
if ((this.column + length) >= maxColumn) {
break;
}
}
}
} else {
FormatJavadocBlock inlinedBlock = (FormatJavadocBlock)node;
length += inlinedBlock.tagEnd - inlinedBlock.sourceStart + 1; // tag length
if (inlinedBlock.reference != null) {
length++; // space between tag and reference
this.scanner.resetTo(inlinedBlock.reference.sourceStart, inlinedBlock.reference.sourceEnd);
int previousToken = -1;
loop:
while (!this.scanner.atEnd()) {
int token = this.scanner.getNextToken();
int tokenLength =
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition)
- this.scanner.startPosition;
switch (token) {
case TerminalTokens.TokenNameWHITESPACE:
if (previousToken == TerminalTokens.TokenNameCOMMA) { // space between method arguments
length++;
}
break;
case TerminalTokens.TokenNameMULTIPLY:
break;
default:
length += tokenLength;
if ((this.column + length) > maxColumn) {
break loop;
}
break;
}
previousToken = token;
}
}
length++; // one more for closing brace
}
if (nodeStart > (previousEnd + 1)) {
length++; // include space between nodes
}
if ((firstColumn + length) >= maxColumn && node == block.nodes[0]) {
// Do not split in this peculiar case as length would be also over the max
// length on next line
return 0;
}
if ((this.column + length) > maxColumn) {
return 1;
}
} catch (InvalidInputException iie) {
// Assume length is one
int tokenLength = 1;
if (nodeStart > (previousEnd + 1)) {
tokenLength++; // include space between nodes
}
if ((this.column + tokenLength) > maxColumn) {
return 1;
}
}
return 0;
}
private void printJavadocBlockReference(FormatJavadocBlock block, FormatJavadocReference reference) {
int maxColumn = this.formatter.preferences.comment_line_length + 1;
boolean headerLine = block.isHeaderLine();
boolean inlined = block.isInlined();
if (headerLine)
maxColumn++;
// First we need to know what is the indentation
this.scanner.resetTo(block.tagEnd + 1, reference.sourceEnd);
this.javadocBlockRefBuffer.setLength(0);
boolean needFormat = false;
int previousToken = -1;
int spacePosition = -1;
String newLineString = null;
int firstColumn = -1;
while (!this.scanner.atEnd()) {
int token;
try {
token = this.scanner.getNextToken();
int tokenLength =
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition)
- this.scanner.startPosition;
switch (token) {
case TerminalTokens.TokenNameWHITESPACE:
if (previousToken != -1 || tokenLength > 1 || this.scanner.currentCharacter != ' ')
needFormat = true;
switch (previousToken) {
case TerminalTokens.TokenNameMULTIPLY:
case TerminalTokens.TokenNameLPAREN:
break;
default: // space between method arguments
spacePosition = this.javadocBlockRefBuffer.length();
// $FALL-THROUGH$ - fall through next case
case -1:
this.javadocBlockRefBuffer.append(' ');
this.column++;
break;
}
break;
case TerminalTokens.TokenNameMULTIPLY:
break;
default:
if (!inlined && spacePosition > 0 && (this.column + tokenLength) > maxColumn) {
// not enough space on the line
this.lastNumberOfNewLines++;
this.line++;
if (newLineString == null) {
this.tempBuffer.setLength(0);
this.tempBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
if (this.commentIndentation != null) {
this.tempBuffer.append(this.commentIndentation);
this.column += this.commentIndentation.length();
}
newLineString = this.tempBuffer.substring(0, this.tempBuffer.length() - 1); // remove last space as buffer
// will be inserted before a
// space
firstColumn = this.column;
} else {
this.column = firstColumn;
}
this.column = firstColumn + this.javadocBlockRefBuffer.length() - spacePosition - 1;
this.javadocBlockRefBuffer.insert(spacePosition, newLineString);
if (headerLine) {
headerLine = false;
maxColumn--;
}
spacePosition = -1;
}
this.javadocBlockRefBuffer.append(this.scanner.source, this.scanner.startPosition, tokenLength);
this.column += tokenLength;
break;
}
previousToken = token;
} catch (InvalidInputException iie) {
// does not happen as syntax is correct
}
}
if (needFormat) {
addReplaceEdit(block.tagEnd + 1, reference.sourceEnd, this.javadocBlockRefBuffer.toString());
}
}
private int getTextLength(FormatJavadocBlock block, FormatJavadocText text) {
// Special case for immutable tags
if (text.isImmutable()) {
this.scanner.resetTo(text.sourceStart, text.sourceEnd);
int textLength = 0;
while (!this.scanner.atEnd()) {
try {
int token = this.scanner.getNextToken();
if (token == TerminalTokens.TokenNameWHITESPACE) {
if (CharOperation.indexOf('\n', this.scanner.source, this.scanner.startPosition,
this.scanner.currentPosition) >= 0) {
textLength = 0;
this.scanner.getNextChar();
if (this.scanner.currentCharacter == '*') {
this.scanner.getNextChar();
if (this.scanner.currentCharacter != ' ') {
textLength++;
}
} else {
textLength++;
}
continue;
}
}
textLength +=
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition)
- this.scanner.startPosition;
} catch (InvalidInputException e) {
// maybe an unterminated string or comment
textLength +=
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition)
- this.scanner.startPosition;
}
}
return textLength;
}
// Simple for one line tags
if (block.isOneLineTag()) {
return text.sourceEnd - text.sourceStart + 1;
}
// Find last line
int startLine = Util.getLineNumber(text.sourceStart, this.lineEnds, 0, this.maxLines);
int endLine = startLine;
int previousEnd = -1;
for (int i = 0; i <= text.separatorsPtr; i++) {
int end = (int)(text.separators[i] >>> 32);
endLine = Util.getLineNumber(end, this.lineEnds, endLine - 1, this.maxLines);
if (endLine > startLine) {
return previousEnd - text.sourceStart + 1;
}
previousEnd = end;
}
// This was a one line text
return text.sourceEnd - text.sourceStart + 1;
}
/* Print and formats a javadoc comments */
void printJavadocComment(int start, int end) {
int lastIndentationLevel = this.indentationLevel;
try {
// parse the comment on the fly
this.scanner.resetTo(start, end - 1);
if (!this.formatterCommentParser.parse(start, end - 1)) {
// problem occurred while parsing the javadoc, early abort formatting
return;
}
FormatJavadoc javadoc = (FormatJavadoc)this.formatterCommentParser.docComment;
// handle indentation
if (this.indentationLevel != 0) {
printIndentationIfNecessary();
}
// handle pending space if any
if (this.pendingSpace) {
addInsertEdit(start, " "); //$NON-NLS-1$
}
if (javadoc.blocks == null) {
// no FormatJavadocTags in this this javadoc
return;
}
// init properly
this.needSpace = false;
this.pendingSpace = false;
int length = javadoc.blocks.length;
// format empty lines between before the first block
FormatJavadocBlock previousBlock = javadoc.blocks[0];
this.lastNumberOfNewLines = 0;
int currentLine = this.line;
int firstBlockStart = previousBlock.sourceStart;
printIndentationIfNecessary(null);
this.column += JAVADOC_HEADER_LENGTH; // consider that the header is already scanned
// If there are several blocks in the javadoc
int index = 1;
if (length > 1) {
// format the description if any
if (previousBlock.isDescription()) {
printJavadocBlock(previousBlock);
FormatJavadocBlock block = javadoc.blocks[index++];
int newLines = this.formatter.preferences.comment_insert_empty_line_before_root_tags ? 2 : 1;
printJavadocGapLines(previousBlock.sourceEnd + 1, block.sourceStart - 1, newLines,
this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, false, null);
previousBlock = block;
}
// format all tags but the last one composing this comment
while (index < length) {
printJavadocBlock(previousBlock);
FormatJavadocBlock block = javadoc.blocks[index++];
printJavadocGapLines(previousBlock.sourceEnd + 1, block.sourceStart - 1, 1,
this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, false, null);
previousBlock = block;
}
}
// format the last block
printJavadocBlock(previousBlock);
// format the header and footer empty spaces
int newLines =
(this.formatter.preferences.comment_new_lines_at_javadoc_boundaries && (this.line > currentLine || javadoc
.isMultiLine())) ? 1 : 0;
printJavadocGapLines(javadoc.textStart, firstBlockStart - 1, newLines,
this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, false, null);
printJavadocGapLines(previousBlock.sourceEnd + 1, javadoc.textEnd, newLines,
this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, true, null);
} finally {
// reset the scanner
this.scanner.resetTo(end, this.scannerEndPosition - 1);
this.needSpace = false;
this.indentationLevel = lastIndentationLevel;
this.lastNumberOfNewLines = 0;
}
}
/*
* prints the empty javadoc line between the 2 given positions. May insert new '*' before each new line
*/
private void printJavadocGapLines(int textStartPosition, int textEndPosition, int newLines, boolean clearBlankLines,
boolean footer, StringBuffer output) {
try {
// If no lines to set in the gap then just insert a space if there's enough room to
if (newLines == 0) {
if (output == null) {
addReplaceEdit(textStartPosition, textEndPosition, " "); //$NON-NLS-1$
} else {
output.append(' ');
}
this.column++;
return;
}
// if there's no enough room to replace text, then insert the gap
if (textStartPosition > textEndPosition) {
if (newLines > 0) {
this.javadocGapLinesBuffer.setLength(0);
for (int i = 0; i < newLines; i++) {
this.javadocGapLinesBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.javadocGapLinesBuffer);
if (footer) {
this.javadocGapLinesBuffer.append(' ');
this.column++;
} else {
this.javadocGapLinesBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
}
}
if (output == null) {
addInsertEdit(textStartPosition, this.javadocGapLinesBuffer.toString());
} else {
output.append(this.javadocGapLinesBuffer);
}
}
return;
}
// There's enough room and some lines to set...
// Skip the text token per token to keep existing stars when possible
this.scanner.resetTo(textStartPosition, textEndPosition);
this.scanner.recordLineSeparator = true;
this.scanner.linePtr = Util.getLineNumber(textStartPosition, this.lineEnds, 0, this.maxLines) - 2;
int linePtr = this.scanner.linePtr;
int lineCount = 0;
int start = textStartPosition;
boolean endsOnMultiply = false;
while (!this.scanner.atEnd()) {
switch (this.scanner.getNextToken()) {
case TerminalTokens.TokenNameMULTIPLY:
// we just need to replace each lines between '*' with the javadoc formatted ones
int linesGap = this.scanner.linePtr - linePtr;
if (linesGap > 0) {
this.javadocGapLinesBuffer.setLength(0);
if (lineCount > 0) {
// TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=49619
this.javadocGapLinesBuffer.append(' ');
}
for (int i = 0; i < linesGap; i++) {
if (clearBlankLines && lineCount >= newLines) {
// leave as the required new lines have been inserted
// so remove any remaining blanks and leave
if (textEndPosition >= start) {
if (output == null) {
addReplaceEdit(start, textEndPosition, this.javadocGapLinesBuffer.toString());
} else {
output.append(this.javadocGapLinesBuffer);
}
}
return;
}
this.javadocGapLinesBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.javadocGapLinesBuffer);
if (i == (linesGap - 1)) {
this.javadocGapLinesBuffer.append(' ');
this.column++;
} else {
this.javadocGapLinesBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
}
lineCount++;
}
int currentTokenStartPosition = this.scanner.getCurrentTokenStartPosition();
int tokenLength = this.scanner.currentPosition - currentTokenStartPosition;
if (output == null) {
addReplaceEdit(start, currentTokenStartPosition - 1, this.javadocGapLinesBuffer.toString());
} else {
output.append(this.javadocGapLinesBuffer);
output.append(this.scanner.source, currentTokenStartPosition, tokenLength);
}
this.column += tokenLength;
if (footer && clearBlankLines && lineCount == newLines) {
if (textEndPosition >= currentTokenStartPosition) {
if (output == null) {
addDeleteEdit(currentTokenStartPosition, textEndPosition);
}
}
return;
}
}
// next start is just after the current token
start = this.scanner.currentPosition;
linePtr = this.scanner.linePtr;
endsOnMultiply = true;
break;
default:
endsOnMultiply = false;
break;
}
}
// Format the last whitespaces
if (lineCount < newLines) {
// Insert new lines as not enough was encountered while scanning the whitespaces
this.javadocGapLinesBuffer.setLength(0);
if (lineCount > 0) {
// TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=49619
this.javadocGapLinesBuffer.append(' ');
}
for (int i = lineCount; i < newLines - 1; i++) {
printJavadocNewLine(this.javadocGapLinesBuffer);
}
this.javadocGapLinesBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.javadocGapLinesBuffer);
if (footer) {
this.javadocGapLinesBuffer.append(' ');
this.column++;
} else {
this.javadocGapLinesBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
}
if (output == null) {
if (textEndPosition >= start) {
addReplaceEdit(start, textEndPosition, this.javadocGapLinesBuffer.toString());
} else {
addInsertEdit(textEndPosition + 1, this.javadocGapLinesBuffer.toString());
}
} else {
output.append(this.javadocGapLinesBuffer);
}
} else {
// Replace all remaining whitespaces by a single space
if (textEndPosition >= start) {
this.javadocGapLinesBuffer.setLength(0);
if (this.scanner.linePtr > linePtr) {
if (lineCount > 0) {
// TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=49619
this.javadocGapLinesBuffer.append(' ');
}
this.javadocGapLinesBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.javadocGapLinesBuffer);
}
this.javadocGapLinesBuffer.append(' ');
if (output == null) {
addReplaceEdit(start, textEndPosition, this.javadocGapLinesBuffer.toString());
} else {
output.append(this.javadocGapLinesBuffer);
}
this.needSpace = false;
} else if (endsOnMultiply) {
if (output == null) {
addInsertEdit(textEndPosition + 1, " "); //$NON-NLS-1$
} else {
output.append(' ');
}
this.needSpace = false;
}
this.column++;
}
} catch (InvalidInputException iie) {
// there's nothing to do if this exception happens
} finally {
this.scanner.recordLineSeparator = false;
this.needSpace = false;
this.scanner.resetTo(textEndPosition + 1, this.scannerEndPosition - 1);
this.lastNumberOfNewLines += newLines;
this.line += newLines;
}
}
private void printJavadocImmutableText(FormatJavadocText text, FormatJavadocBlock block, boolean textOnNewLine) {
try {
// Iterate on text line separators
int textLineStart = text.lineStart;
this.scanner.tokenizeWhiteSpace = false;
String newLineString = null;
for (int idx = 0, max = text.separatorsPtr; idx <= max; idx++) {
int start = (int)text.separators[idx];
int lineStart = Util.getLineNumber(start, this.lineEnds, textLineStart - 1, this.maxLines);
while (textLineStart < lineStart) {
int end = this.lineEnds[textLineStart - 1];
this.scanner.resetTo(end, start);
int token = this.scanner.getNextToken();
switch (token) {
case TerminalTokens.TokenNameMULTIPLY:
case TerminalTokens.TokenNameMULTIPLY_EQUAL:
break;
default:
return;
}
if (this.scanner.currentCharacter == ' ') {
this.scanner.getNextChar();
}
if (newLineString == null) {
this.tempBuffer.setLength(0);
this.column = 1;
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
newLineString = this.tempBuffer.toString();
}
addReplaceEdit(end + 1, this.scanner.getCurrentTokenEndPosition(), newLineString);
textLineStart =
Util.getLineNumber(this.scanner.currentPosition - 1, this.lineEnds, textLineStart, this.maxLines);
}
}
} catch (InvalidInputException iie) {
// leave
} finally {
// Reset
this.needSpace = false;
this.scanner.tokenizeWhiteSpace = true;
this.scanner.resetTo(text.sourceEnd + 1, this.scannerEndPosition - 1);
}
}
/*
* Print the gap lines for an immutable block. That's needed to be specific as the formatter needs to keep white spaces if
* possible except those which are indentation ones. Note that in the peculiar case of a two lines immutable tag (multi lines
* block), the formatter will join the two lines.
*/
private void printJavadocGapLinesForImmutableBlock(FormatJavadocBlock block) {
// Init
int firstLineEnd = -1; // not initialized
int newLineStart = -1; // not initialized
int secondLineStart = -1; // not initialized
int starPosition = -1; // not initialized
int offset = 0;
int start = block.tagEnd + 1;
int end = block.nodes[0].sourceStart - 1;
this.scanner.resetTo(start, end);
int lineStart = block.lineStart;
int lineEnd = Util.getLineNumber(block.nodes[0].sourceEnd, this.lineEnds, lineStart - 1, this.maxLines);
boolean multiLinesBlock = lineEnd > (lineStart + 1);
int previousPosition = this.scanner.currentPosition;
String newLineString = null;
int indentationColumn = 0;
int leadingSpaces = -1;
// Scan the existing gap
while (!this.scanner.atEnd()) {
char ch = (char)this.scanner.getNextChar();
switch (ch) {
case '\t':
// increase the corresponding counter from the appropriate tab value
if (secondLineStart > 0 || firstLineEnd < 0) {
int reminder = this.tabLength == 0 ? 0 : offset % this.tabLength;
if (reminder == 0) {
offset += this.tabLength;
} else {
offset = ((offset / this.tabLength) + 1) * this.tabLength;
}
} else if (leadingSpaces >= 0) {
int reminder = this.tabLength == 0 ? 0 : offset % this.tabLength;
if (reminder == 0) {
leadingSpaces += this.tabLength;
} else {
leadingSpaces = ((offset / this.tabLength) + 1) * this.tabLength;
}
}
break;
case '\r':
case '\n':
// new line, store the end of the first one
if (firstLineEnd < 0) {
firstLineEnd = previousPosition;
}
// print indentation if there were spaces without any star on the line
if (leadingSpaces > 0 && multiLinesBlock) {
if (newLineString == null) {
this.column = 1;
this.tempBuffer.setLength(0);
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
newLineString = this.tempBuffer.toString();
indentationColumn = this.column;
} else {
this.column = indentationColumn;
}
addReplaceEdit(newLineStart, newLineStart + indentationColumn - 2, newLineString);
}
// store line start and reset positions
newLineStart = this.scanner.currentPosition;
leadingSpaces = 0;
starPosition = -1;
if (multiLinesBlock) {
offset = 0;
secondLineStart = -1;
}
break;
case '*':
// store line start position if this is the first star of the line
if (starPosition < 0 && firstLineEnd > 0) {
secondLineStart = this.scanner.currentPosition;
starPosition = this.scanner.currentPosition;
leadingSpaces = -1;
}
break;
default:
// increment offset if line has started
if (secondLineStart > 0) {
// skip first white space after the first '*'
if (secondLineStart == starPosition) {
secondLineStart = this.scanner.currentPosition;
} else {
// print indentation before the following characters
if (offset == 0 && multiLinesBlock) {
if (newLineString == null) {
this.tempBuffer.setLength(0);
this.column = 1;
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
indentationColumn = this.column;
newLineString = this.tempBuffer.toString();
} else {
this.column = indentationColumn;
}
addReplaceEdit(newLineStart, secondLineStart - 1, newLineString);
}
offset++;
}
} else if (firstLineEnd < 0) {
// no new line yet, increment the offset
offset++;
} else if (leadingSpaces >= 0) {
// no star yet, increment the leading spaces
leadingSpaces++;
}
break;
}
previousPosition = this.scanner.currentPosition;
}
// Increment the columns from the numbers of characters counted on the line
if (multiLinesBlock) {
this.column += offset;
} else {
this.column++;
}
// Replace the new line with a single space when there's only one separator
// or, if necessary, print the indentation on the last line
if (!multiLinesBlock) {
if (firstLineEnd > 0) {
addReplaceEdit(firstLineEnd, end, " "); //$NON-NLS-1$
}
} else if (secondLineStart > 0) {
if (newLineString == null) {
this.tempBuffer.setLength(0);
this.column = 1;
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
newLineString = this.tempBuffer.toString();
indentationColumn = this.column;
} else {
this.column = indentationColumn;
}
addReplaceEdit(newLineStart, secondLineStart - 1, newLineString);
} else if (leadingSpaces > 0) {
if (newLineString == null) {
this.tempBuffer.setLength(0);
this.column = 1;
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
newLineString = this.tempBuffer.toString();
indentationColumn = this.column;
} else {
this.column = indentationColumn;
}
addReplaceEdit(newLineStart, newLineStart + indentationColumn - 2, newLineString);
}
// Reset
this.needSpace = false;
this.scanner.resetTo(end + 1, this.scannerEndPosition - 1);
}
private int printJavadocHtmlTag(FormatJavadocText text, FormatJavadocBlock block, boolean textOnNewLine) {
// Compute indentation if necessary
boolean clearBlankLines = this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment;
// Local variables init
int textStart = text.sourceStart;
int nextStart = textStart;
int startLine = Util.getLineNumber(textStart, this.lineEnds, 0, this.maxLines);
int htmlTagID = text.getHtmlTagID();
if (text.depth >= this.javadocHtmlTagBuffers.length) {
int length = this.javadocHtmlTagBuffers.length;
System.arraycopy(this.javadocHtmlTagBuffers, 0, this.javadocHtmlTagBuffers = new StringBuffer[text.depth + 6],
0, length);
}
StringBuffer buffer = this.javadocHtmlTagBuffers[text.depth];
if (buffer == null) {
buffer = new StringBuffer();
this.javadocHtmlTagBuffers[text.depth] = buffer;
} else {
buffer.setLength(0);
}
// New line will be added before next node
int max = text.separatorsPtr;
int linesAfter = 0;
int previousEnd = -1;
boolean isHtmlBreakTag = htmlTagID == JAVADOC_SINGLE_BREAK_TAG_ID;
boolean isHtmlSeparatorTag = htmlTagID == JAVADOC_SEPARATOR_TAGS_ID;
if (isHtmlBreakTag) {
return 1;
}
// Iterate on text line separators
boolean isCode = htmlTagID == JAVADOC_CODE_TAGS_ID;
for (int idx = 0, ptr = 0; idx <= max || (text.htmlNodesPtr != -1 && ptr <= text.htmlNodesPtr); idx++) {
// append text to buffer realigning with the line length
int end = (idx > max) ? text.sourceEnd : (int)(text.separators[idx] >>> 32);
int nodeKind = 0; // text break
if (text.htmlNodesPtr >= 0 && ptr <= text.htmlNodesPtr && end > text.htmlNodes[ptr].sourceStart) {
FormatJavadocNode node = text.htmlNodes[ptr];
FormatJavadocText htmlTag = node.isText() ? (FormatJavadocText)node : null;
int newLines = htmlTag == null ? 0 : htmlTag.linesBefore;
if (linesAfter > newLines) {
newLines = linesAfter;
if (newLines > 1 && clearBlankLines) {
if (idx < 2 || (text.htmlIndexes[idx - 2] & JAVADOC_TAGS_ID_MASK) != JAVADOC_CODE_TAGS_ID) {
newLines = 1;
}
}
}
if (textStart < previousEnd) {
addReplaceEdit(textStart, previousEnd, buffer.toString());
}
boolean immutable = node.isImmutable();
if (newLines == 0) {
newLines = printJavadocBlockNodesNewLines(block, node, previousEnd);
}
int nodeStart = node.sourceStart;
if (newLines > 0 || (idx > 1 && nodeStart > (previousEnd + 1))) {
printJavadocGapLines(previousEnd + 1, nodeStart - 1, newLines, clearBlankLines, false, null);
}
if (newLines > 0)
textOnNewLine = true;
buffer.setLength(0);
if (node.isText()) {
if (immutable) {
// do not change immutable tags, just increment column
if (textOnNewLine && this.commentIndentation != null) {
addInsertEdit(node.sourceStart, this.commentIndentation);
this.column += this.commentIndentation.length();
}
printJavadocImmutableText(htmlTag, block, textOnNewLine);
this.column += getTextLength(block, htmlTag);
linesAfter = 0;
} else {
linesAfter = printJavadocHtmlTag(htmlTag, block, textOnNewLine);
}
nodeKind = 1; // text
} else {
if (textOnNewLine && this.commentIndentation != null) {
addInsertEdit(node.sourceStart, this.commentIndentation);
this.column += this.commentIndentation.length();
}
printJavadocBlock((FormatJavadocBlock)node);
linesAfter = 0;
nodeKind = 2; // block
}
textStart = node.sourceEnd + 1;
ptr++;
if (idx > max) {
return linesAfter;
}
} else {
if (idx > 0 && linesAfter > 0) {
printJavadocGapLines(previousEnd + 1, nextStart - 1, linesAfter, clearBlankLines, false, buffer);
textOnNewLine = true;
}
boolean needIndentation = textOnNewLine;
if (idx > 0) {
if (!needIndentation && text.isTextAfterHtmlSeparatorTag(idx - 1)) {
needIndentation = true;
}
}
this.needSpace = idx > 1 && (previousEnd + 1) < nextStart; // There's no space between text and html tag or inline
// block => do not insert space a the beginning of the text
printJavadocTextLine(buffer, nextStart, end, block, idx == 0, needIndentation, idx == 0/*
* opening html tag ?
*/
|| text.htmlIndexes[idx - 1] != -1);
linesAfter = 0;
if (idx == 0) {
if (isHtmlSeparatorTag) {
linesAfter = 1;
}
} else if (text.htmlIndexes[idx - 1] == JAVADOC_SINGLE_BREAK_TAG_ID) {
linesAfter = 1;
}
}
// Replace with current buffer if there are several empty lines between text lines
nextStart = (int)text.separators[idx];
int endLine = Util.getLineNumber(end, this.lineEnds, startLine - 1, this.maxLines);
startLine = Util.getLineNumber(nextStart, this.lineEnds, endLine - 1, this.maxLines);
int linesGap = startLine - endLine;
if (linesGap > 0) {
if (clearBlankLines) {
// keep previously computed lines after
} else {
if (idx == 0
|| linesGap > 1
||
(idx < max && nodeKind == 1 && (text.htmlIndexes[idx - 1] & JAVADOC_TAGS_ID_MASK) != JAVADOC_IMMUTABLE_TAGS_ID)) {
if (linesAfter < linesGap) {
linesAfter = linesGap;
}
}
}
}
textOnNewLine = linesAfter > 0;
// print <pre> tag
if (isCode) {
int codeEnd = (int)(text.separators[max] >>> 32);
if (codeEnd > end) {
if (this.formatter.preferences.comment_format_source) {
if (textStart < end)
addReplaceEdit(textStart, end, buffer.toString());
// See whether there's a space before the code
if (linesGap > 0) {
int lineStart = this.scanner.getLineStart(startLine);
if (nextStart > lineStart) { // if code starts at the line, then no leading space is needed
this.scanner.resetTo(lineStart, nextStart - 1);
try {
int token = this.scanner.getNextToken();
if (token == TerminalTokens.TokenNameWHITESPACE) {
// skip indentation
token = this.scanner.getNextToken();
}
if (token == TerminalTokens.TokenNameMULTIPLY) {
nextStart = this.scanner.currentPosition;
}
} catch (InvalidInputException iie) {
// skip
}
}
}
// Format gap lines before code
int newLines = linesGap;
if (newLines == 0)
newLines = 1;
this.needSpace = false;
printJavadocGapLines(end + 1, nextStart - 1, newLines, false/*
* clear first blank lines inside < pre > tag as
* done by old formatter
*/, false, null);
// Format the code
printCodeSnippet(nextStart, codeEnd, linesGap);
// Format the gap lines after the code
nextStart = (int)text.separators[max];
printJavadocGapLines(codeEnd + 1, nextStart - 1, 1, false/*
* clear blank lines inside < pre > tag as done by old
* formatter
*/, false, null);
return 2;
}
} else {
nextStart = (int)text.separators[max];
if ((nextStart - 1) > (end + 1)) {
int line1 = Util.getLineNumber(end + 1, this.lineEnds, startLine - 1, this.maxLines);
int line2 = Util.getLineNumber(nextStart - 1, this.lineEnds, line1 - 1, this.maxLines);
int gapLines = line2 - line1 - 1;
printJavadocGapLines(end + 1, nextStart - 1, gapLines, false/*
* never clear blank lines inside < pre > tag
*/, false, null);
if (gapLines > 0)
textOnNewLine = true;
}
}
return 1;
}
// store previous end
previousEnd = end;
}
// Insert last gap
boolean closingTag =
isHtmlBreakTag || (text.htmlIndexes != null && (text.htmlIndexes[max] & JAVADOC_TAGS_ID_MASK) == htmlTagID);
boolean isValidHtmlSeparatorTag = max > 0 && isHtmlSeparatorTag && closingTag;
if (previousEnd != -1) {
if (isValidHtmlSeparatorTag) {
if (linesAfter == 0)
linesAfter = 1;
}
if (linesAfter > 0) {
printJavadocGapLines(previousEnd + 1, nextStart - 1, linesAfter, clearBlankLines, false, buffer);
textOnNewLine = linesAfter > 0;
}
}
// Print closing tag
boolean needIndentation = textOnNewLine;
if (!needIndentation && !isHtmlBreakTag && text.htmlIndexes != null && text.isTextAfterHtmlSeparatorTag(max)) {
needIndentation = true;
}
this.needSpace = !closingTag && max > 0 // not a single or not closed tag (e.g. <br>)
&& (previousEnd + 1) <
nextStart; // There's no space between text and html tag or inline block => do not insert space a
// the beginning of the text
printJavadocTextLine(buffer, nextStart, text.sourceEnd, block, max <= 0, needIndentation, closingTag/*
* closing html tag
*/);
if (textStart < text.sourceEnd) {
addReplaceEdit(textStart, text.sourceEnd, buffer.toString());
}
// Reset
this.needSpace = false;
this.scanner.resetTo(text.sourceEnd + 1, this.scannerEndPosition - 1);
// Return the new lines to insert after
return isValidHtmlSeparatorTag ? 1 : 0;
}
private void printJavadocNewLine(StringBuffer buffer) {
buffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(buffer);
buffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
this.line++;
this.lastNumberOfNewLines++;
}
private void printJavadocText(FormatJavadocText text, FormatJavadocBlock block, boolean textOnNewLine) {
boolean clearBlankLines = this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment;
boolean joinLines = this.formatter.preferences.join_lines_in_comments;
this.javadocTextBuffer.setLength(0);
int textStart = text.sourceStart;
int nextStart = textStart;
int startLine = Util.getLineNumber(textStart, this.lineEnds, 0, this.maxLines);
// Iterate on text line separators
for (int idx = 0, max = text.separatorsPtr; idx <= max; idx++) {
// append text to buffer realigning with the line length
int end = (int)(text.separators[idx] >>> 32);
boolean needIndentation = textOnNewLine;
if (idx > 0) {
if (!needIndentation && text.isTextAfterHtmlSeparatorTag(idx - 1)) {
needIndentation = true;
}
}
this.needSpace = idx > 0;
printJavadocTextLine(this.javadocTextBuffer, nextStart, end, block,
idx == 0 || (!joinLines && textOnNewLine)/* first text? */, needIndentation, false /*
* not an html tag
*/);
textOnNewLine = false;
// Replace with current buffer if there are several empty lines between text lines
nextStart = (int)text.separators[idx];
if (!clearBlankLines || !joinLines) {
int endLine = Util.getLineNumber(end, this.lineEnds, startLine - 1, this.maxLines);
startLine = Util.getLineNumber(nextStart, this.lineEnds, endLine - 1, this.maxLines);
int gapLine = endLine;
if (joinLines)
gapLine++; // if not preserving line break then gap must be at least of one line
if (startLine > gapLine) {
addReplaceEdit(textStart, end, this.javadocTextBuffer.toString());
textStart = nextStart;
this.javadocTextBuffer.setLength(0);
int newLines = startLine - endLine;
if (clearBlankLines)
newLines = 1;
printJavadocGapLines(end + 1, nextStart - 1, newLines,
this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, false, null);
textOnNewLine = true;
} else if (startLine > endLine) {
textOnNewLine = !joinLines;
}
}
}
// Replace remaining line
boolean needIndentation = textOnNewLine;
this.needSpace = text.separatorsPtr >= 0;
printJavadocTextLine(this.javadocTextBuffer, nextStart, text.sourceEnd, block, text.separatorsPtr == -1 /*
* first text ?
*/,
needIndentation, false /* not an html tag */);
// TODO Bring back following optimization
// if (lastNewLines != this.lastNumberOfNewLines || (this.column - currentColumn) != (text.sourceEnd - text.sourceStart +
// 1)) {
addReplaceEdit(textStart, text.sourceEnd, this.javadocTextBuffer.toString());
// }
// Reset
this.needSpace = false;
this.scanner.resetTo(text.sourceEnd + 1, this.scannerEndPosition - 1);
}
/* Returns whether the text has been modified or not. */
private void printJavadocTextLine(StringBuffer buffer, int textStart, int textEnd, FormatJavadocBlock block,
boolean firstText, boolean needIndentation, boolean isHtmlTag) {
boolean headerLine = block.isHeaderLine() && this.lastNumberOfNewLines == 0;
// First we need to know what is the indentation
this.javadocTokensBuffer.setLength(0);
int firstColumn = 1 + this.indentationLevel + BLOCK_LINE_PREFIX_LENGTH;
int maxColumn = this.formatter.preferences.comment_line_length + 1;
if (headerLine) {
firstColumn++;
maxColumn++;
}
if (needIndentation && this.commentIndentation != null) {
buffer.append(this.commentIndentation);
this.column += this.commentIndentation.length();
firstColumn += this.commentIndentation.length();
}
if (this.column < firstColumn) {
this.column = firstColumn;
}
// Scan the text token per token to compact it and size it the max line length
String newLineString = null;
try {
this.scanner.resetTo(textStart, textEnd);
this.scanner.skipComments = true;
int previousToken = -1;
boolean textOnNewLine = needIndentation;
// Consume text token per token
while (!this.scanner.atEnd()) {
int token;
try {
token = this.scanner.getNextToken();
} catch (InvalidInputException iie) {
token = consumeInvalidToken(textEnd);
}
int tokensBufferLength = this.javadocTokensBuffer.length();
int tokenStart = this.scanner.getCurrentTokenStartPosition();
int tokenLength =
(this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - tokenStart;
boolean insertSpace =
(previousToken == TerminalTokens.TokenNameWHITESPACE || this.needSpace) && !textOnNewLine;
String tokensBufferString = this.javadocTokensBuffer.toString().trim();
switch (token) {
case TerminalTokens.TokenNameWHITESPACE:
if (tokensBufferLength > 0) {
boolean shouldSplit = (this.column + tokensBufferLength) > maxColumn // the max length is reached
&& !isHtmlTag && (insertSpace || tokensBufferLength > 1)
// allow to split at the beginning only when
// starting with an identifier or a token with a
// length > 1
&& tokensBufferString.charAt(0) != '@'; // avoid to split just before a '@'
if (shouldSplit) {
this.lastNumberOfNewLines++;
this.line++;
if (newLineString == null) {
this.tempBuffer.setLength(0);
this.tempBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
if (this.commentIndentation != null) {
this.tempBuffer.append(this.commentIndentation);
this.column += this.commentIndentation.length();
}
firstColumn = this.column;
newLineString = this.tempBuffer.toString();
} else {
this.column = firstColumn;
}
buffer.append(newLineString);
buffer.append(tokensBufferString);
this.column += tokensBufferString.length();
if (headerLine) {
firstColumn--;
maxColumn--;
headerLine = false;
}
} else {
buffer.append(this.javadocTokensBuffer);
this.column += tokensBufferLength;
}
this.javadocTokensBuffer.setLength(0);
}
textOnNewLine = false;
previousToken = token;
continue;
case TerminalTokens.TokenNameCharacterLiteral:
if (this.scanner.currentPosition > this.scanner.eofPosition) {
this.scanner.resetTo(this.scanner.startPosition, textEnd);
this.scanner.getNextChar();
token = 1;
}
break;
}
int lastColumn = this.column + tokensBufferLength + tokenLength;
if (insertSpace)
lastColumn++;
boolean shouldSplit =
lastColumn > maxColumn // the max length is reached
&& (!isHtmlTag || previousToken == -1) // not an html tag or just at the beginning of it
&& token != TerminalTokens.TokenNameAT
&& (tokensBufferLength == 0 || this.javadocTokensBuffer.charAt(tokensBufferLength - 1) != '@'); // avoid to
// split just
// before a '@'
if (shouldSplit) {
// not enough space on the line
if ((tokensBufferLength > 0 || tokenLength < maxColumn) && !isHtmlTag && tokensBufferLength > 0
&& (firstColumn + tokensBufferLength + tokenLength) >= maxColumn) {
// there won't be enough room even if we break the line before the buffered tokens
// So add the buffered tokens now
buffer.append(this.javadocTokensBuffer);
this.column += tokensBufferLength;
this.javadocTokensBuffer.setLength(0);
tokensBufferLength = 0;
textOnNewLine = false;
}
if ((tokensBufferLength > 0 || /*
* (firstColumn+tokenLength) < maxColumn || (insertSpace &&
*/this.column > firstColumn) && (!textOnNewLine || !firstText)) {
this.lastNumberOfNewLines++;
this.line++;
if (newLineString == null) {
this.tempBuffer.setLength(0);
this.tempBuffer.append(this.lineSeparator);
this.column = 1;
printIndentationIfNecessary(this.tempBuffer);
this.tempBuffer.append(BLOCK_LINE_PREFIX);
this.column += BLOCK_LINE_PREFIX_LENGTH;
if (this.commentIndentation != null) {
this.tempBuffer.append(this.commentIndentation);
this.column += this.commentIndentation.length();
}
firstColumn = this.column;
newLineString = this.tempBuffer.toString();
} else {
this.column = firstColumn;
}
buffer.append(newLineString);
}
if (tokensBufferLength > 0) {
String tokensString = tokensBufferString;
buffer.append(tokensString);
this.column += tokensString.length();
this.javadocTokensBuffer.setLength(0);
tokensBufferLength = 0;
}
buffer.append(this.scanner.source, tokenStart, tokenLength);
this.column += tokenLength;
textOnNewLine = false;
if (headerLine) {
firstColumn--;
maxColumn--;
headerLine = false;
}
} else {
// append token to the line
if (insertSpace) {
this.javadocTokensBuffer.append(' ');
}
this.javadocTokensBuffer.append(this.scanner.source, tokenStart, tokenLength);
}
previousToken = token;
this.needSpace = false;
if (headerLine && lastColumn == maxColumn && this.scanner.atEnd()) {
this.lastNumberOfNewLines++;
this.line++;
}
}
} finally {
this.scanner.skipComments = false;
// Add remaining buffered tokens
if (this.javadocTokensBuffer.length() > 0) {
buffer.append(this.javadocTokensBuffer);
this.column += this.javadocTokensBuffer.length();
}
}
}
public void printModifiers(Annotation[] annotations, ASTVisitor visitor) {
printModifiers(annotations, visitor, ICodeFormatterConstants.ANNOTATION_UNSPECIFIED);
}
public void printModifiers(Annotation[] annotations, ASTVisitor visitor, int annotationSourceKind) {
try {
int annotationsLength = annotations != null ? annotations.length : 0;
int annotationsIndex = 0;
boolean isFirstModifier = true;
int currentTokenStartPosition = this.scanner.currentPosition;
boolean hasComment = false;
boolean hasModifiers = false;
while ((this.currentToken = this.scanner.getNextToken()) != TerminalTokens.TokenNameEOF) {
int foundTaskCount = this.scanner.foundTaskCount;
int tokenStartPosition = this.scanner.getCurrentTokenStartPosition();
int tokenEndPosition = this.scanner.getCurrentTokenEndPosition();
switch (this.currentToken) {
case TerminalTokens.TokenNamepublic:
case TerminalTokens.TokenNameprotected:
case TerminalTokens.TokenNameprivate:
case TerminalTokens.TokenNamestatic:
case TerminalTokens.TokenNameabstract:
case TerminalTokens.TokenNamefinal:
case TerminalTokens.TokenNamenative:
case TerminalTokens.TokenNamesynchronized:
case TerminalTokens.TokenNametransient:
case TerminalTokens.TokenNamevolatile:
case TerminalTokens.TokenNamestrictfp:
hasModifiers = true;
print(this.scanner.currentPosition - this.scanner.startPosition, !isFirstModifier);
isFirstModifier = false;
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameAT:
hasModifiers = true;
if (!isFirstModifier) {
space();
}
this.scanner.resetTo(this.scanner.getCurrentTokenStartPosition(), this.scannerEndPosition - 1);
if (annotationsIndex < annotationsLength) {
boolean insertSpaceBeforeBrace =
this.formatter.preferences.insert_space_before_opening_brace_in_array_initializer;
this.formatter.preferences.insert_space_before_opening_brace_in_array_initializer = false;
try {
annotations[annotationsIndex++].traverse(visitor, (BlockScope)null);
} finally {
this.formatter.preferences.insert_space_before_opening_brace_in_array_initializer =
insertSpaceBeforeBrace;
}
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=122247
boolean shouldAddNewLine = false;
switch (annotationSourceKind) {
case ICodeFormatterConstants.ANNOTATION_ON_TYPE:
if (this.formatter.preferences.insert_new_line_after_annotation_on_type) {
shouldAddNewLine = true;
}
break;
case ICodeFormatterConstants.ANNOTATION_ON_FIELD:
if (this.formatter.preferences.insert_new_line_after_annotation_on_field) {
shouldAddNewLine = true;
}
break;
case ICodeFormatterConstants.ANNOTATION_ON_METHOD:
if (this.formatter.preferences.insert_new_line_after_annotation_on_method) {
shouldAddNewLine = true;
}
break;
case ICodeFormatterConstants.ANNOTATION_ON_PACKAGE:
if (this.formatter.preferences.insert_new_line_after_annotation_on_package) {
shouldAddNewLine = true;
}
break;
case ICodeFormatterConstants.ANNOTATION_ON_PARAMETER:
if (this.formatter.preferences.insert_new_line_after_annotation_on_parameter) {
shouldAddNewLine = true;
}
break;
case ICodeFormatterConstants.ANNOTATION_ON_LOCAL_VARIABLE:
if (this.formatter.preferences.insert_new_line_after_annotation_on_local_variable) {
shouldAddNewLine = true;
}
break;
default:
// do nothing when no annotation formatting option specified
}
if (shouldAddNewLine) {
this.printNewLine();
}
} else {
return;
}
isFirstModifier = false;
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameCOMMENT_BLOCK:
case TerminalTokens.TokenNameCOMMENT_JAVADOC:
if (this.useTags && this.editsEnabled) {
boolean turnOff = false;
if (foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
turnOff = true;
} else if (this.tagsKind == this.currentToken
&& CharOperation.equals(this.disablingTag, this.scanner.source, tokenStartPosition,
tokenEndPosition + 1)) {
this.editsEnabled = false;
turnOff = true;
}
if (turnOff) {
if (!this.editsEnabled && this.editsIndex > 1) {
OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex - 1];
if (this.scanner.startPosition == currentEdit.offset + currentEdit.length) {
printNewLinesBeforeDisablingComment();
}
}
}
}
printBlockComment(this.currentToken == TerminalTokens.TokenNameCOMMENT_JAVADOC);
if (this.useTags && !this.editsEnabled) {
if (foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
} else if (this.tagsKind == this.currentToken) {
this.editsEnabled =
CharOperation.equals(this.enablingTag, this.scanner.source, tokenStartPosition,
tokenEndPosition + 1);
}
}
currentTokenStartPosition = this.scanner.currentPosition;
hasComment = true;
break;
case TerminalTokens.TokenNameCOMMENT_LINE:
tokenEndPosition = -this.scanner.commentStops[this.scanner.commentPtr];
if (this.useTags && this.editsEnabled) {
boolean turnOff = false;
if (foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
turnOff = true;
} else if (this.tagsKind == this.currentToken
&& CharOperation.equals(this.disablingTag, this.scanner.source, tokenStartPosition,
tokenEndPosition)) {
this.editsEnabled = false;
turnOff = true;
}
if (turnOff) {
if (!this.editsEnabled && this.editsIndex > 1) {
OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex - 1];
if (this.scanner.startPosition == currentEdit.offset + currentEdit.length) {
printNewLinesBeforeDisablingComment();
}
}
}
}
printLineComment();
if (this.useTags && !this.editsEnabled) {
if (foundTaskCount > 0) {
setEditsEnabled(foundTaskCount);
} else if (this.tagsKind == this.currentToken) {
this.editsEnabled =
CharOperation.equals(this.enablingTag, this.scanner.source, tokenStartPosition,
tokenEndPosition);
}
}
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameWHITESPACE:
addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
int count = 0;
char[] whiteSpaces = this.scanner.getCurrentTokenSource();
for (int i = 0, max = whiteSpaces.length; i < max; i++) {
switch (whiteSpaces[i]) {
case '\r':
if ((i + 1) < max) {
if (whiteSpaces[i + 1] == '\n') {
i++;
}
}
count++;
break;
case '\n':
count++;
}
}
if (count >= 1 && hasComment) {
printNewLine();
}
currentTokenStartPosition = this.scanner.currentPosition;
hasComment = false;
break;
default:
if (hasModifiers) {
space();
}
// step back one token
this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
return;
}
}
} catch (InvalidInputException e) {
throw new AbortFormatting(e);
}
}
public void printNewLine() {
this.printNewLine(this.scanner.getCurrentTokenEndPosition() + 1);
}
public void printNewLine(int insertPosition) {
if (this.nlsTagCounter > 0) {
return;
}
if (this.lastNumberOfNewLines >= 1) {
// ensure that the scribe is at the beginning of a new line
// only if no specific indentation has been previously set
if (!this.preserveLineBreakIndentation) {
this.column = 1;
}
this.preserveLineBreakIndentation = false;
return;
}
addInsertEdit(insertPosition, this.lineSeparator);
this.line++;
this.lastNumberOfNewLines = 1;
this.column = 1;
this.needSpace = false;
this.pendingSpace = false;
this.preserveLineBreakIndentation = false;
this.lastLineComment.contiguous = false;
}
/* Print the indentation of a disabling comment */
private void printNewLinesBeforeDisablingComment() {
// Get the beginning of comment line
int linePtr = Arrays.binarySearch(this.lineEnds, this.scanner.startPosition);
if (linePtr < 0) {
linePtr = -linePtr - 1;
}
int indentation = 0;
int beginningOfLine = getLineEnd(linePtr) + 1;
if (beginningOfLine == -1) {
beginningOfLine = 0;
}
// If the comment is in the middle of the line, then there's nothing to do
OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex - 1];
int offset = currentEdit.offset;
if (offset >= beginningOfLine)
return;
// Compute the comment indentation
int scannerStartPosition = this.scanner.startPosition;
int scannerEofPosition = this.scanner.eofPosition;
int scannerCurrentPosition = this.scanner.currentPosition;
char scannerCurrentChar = this.scanner.currentCharacter;
int length = currentEdit.length;
this.scanner.resetTo(beginningOfLine, offset + length - 1);
try {
while (!this.scanner.atEnd()) {
char ch = (char)this.scanner.getNextChar();
switch (ch) {
case '\t':
if (this.tabLength != 0) {
int reminder = indentation % this.tabLength;
if (reminder == 0) {
indentation += this.tabLength;
} else {
indentation = ((indentation / this.tabLength) + 1) * this.tabLength;
}
}
break;
case ' ':
indentation++;
break;
default:
// Should not happen as the offset of the edit is before the beginning of line
return;
}
}
// Split the existing edit to keep the change before the beginning of the last line
// but change the indentation after. Note that at this stage, the add*Edit methods
// cannot be longer used as the edits are disabled
String indentationString;
int currentIndentation = getCurrentIndentation(this.scanner.currentPosition);
if (currentIndentation > 0 && this.indentationLevel > 0) {
int col = this.column;
this.tempBuffer.setLength(0);
printIndentationIfNecessary(this.tempBuffer);
indentationString = this.tempBuffer.toString();
this.column = col;
} else {
indentationString = Util.EMPTY_STRING;
}
String replacement = currentEdit.replacement;
if (replacement.length() == 0) {
// previous edit was a delete, as we're sure to have a new line before
// the comment, then the edit needs to be either replaced entirely with
// the expected indentation
this.edits[this.editsIndex - 1] =
new OptimizedReplaceEdit(beginningOfLine, offset + length - beginningOfLine, indentationString);
} else {
int idx = replacement.lastIndexOf(this.lineSeparator);
if (idx >= 0) {
// replace current edit if it contains a line separator
int start = idx + this.lsLength;
this.tempBuffer.setLength(0);
this.tempBuffer.append(replacement.substring(0, start));
if (indentationString != Util.EMPTY_STRING) {
this.tempBuffer.append(indentationString);
}
this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(offset, length, this.tempBuffer.toString());
}
}
} finally {
this.scanner.startPosition = scannerStartPosition;
this.scanner.eofPosition = scannerEofPosition;
this.scanner.currentPosition = scannerCurrentPosition;
this.scanner.currentCharacter = scannerCurrentChar;
}
}
/*
* Print new lines characters when the edits are disabled. In this case, only the line separator is replaced if necessary, the
* other white spaces are untouched.
*/
private boolean printNewLinesCharacters(int offset, int length) {
boolean foundNewLine = false;
int scannerStartPosition = this.scanner.startPosition;
int scannerEofPosition = this.scanner.eofPosition;
int scannerCurrentPosition = this.scanner.currentPosition;
char scannerCurrentChar = this.scanner.currentCharacter;
this.scanner.resetTo(offset, offset + length - 1);
try {
while (!this.scanner.atEnd()) {
int start = this.scanner.currentPosition;
char ch = (char)this.scanner.getNextChar();
boolean needReplace = ch != this.firstLS;
switch (ch) {
case '\r':
if (this.scanner.atEnd())
break;
ch = (char)this.scanner.getNextChar();
if (ch != '\n')
break;
needReplace = needReplace || this.lsLength != 2;
//$FALL-THROUGH$
case '\n':
if (needReplace) {
if (this.editsIndex == 0 || this.edits[this.editsIndex - 1].offset != start) {
this.edits[this.editsIndex++] =
new OptimizedReplaceEdit(start, this.scanner.currentPosition - start, this.lineSeparator);
}
}
foundNewLine = true;
break;
}
}
} finally {
this.scanner.startPosition = scannerStartPosition;
this.scanner.eofPosition = scannerEofPosition;
this.scanner.currentPosition = scannerCurrentPosition;
this.scanner.currentCharacter = scannerCurrentChar;
}
return foundNewLine;
}
public void printNextToken(int expectedTokenType) {
printNextToken(expectedTokenType, false);
}
public void printNextToken(int expectedTokenType, boolean considerSpaceIfAny) {
printNextToken(expectedTokenType, considerSpaceIfAny, PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION);
}
public void printNextToken(int expectedTokenType, boolean considerSpaceIfAny, int emptyLineRules) {
// Set brace flag, it's useful for the scribe while preserving line breaks
printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT, emptyLineRules);
try {
this.currentToken = this.scanner.getNextToken();
if (expectedTokenType != this.currentToken) {
throw new AbortFormatting(
"unexpected token type, expecting:" + expectedTokenType + ", actual:" +
this.currentToken);//$NON-NLS-1$//$NON-NLS-2$
}
print(this.scanner.currentPosition - this.scanner.startPosition, considerSpaceIfAny);
} catch (InvalidInputException e) {
throw new AbortFormatting(e);
}
}
public void printNextToken(int[] expectedTokenTypes) {
printNextToken(expectedTokenTypes, false);
}
public void printNextToken(int[] expectedTokenTypes, boolean considerSpaceIfAny) {
printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT);
try {
this.currentToken = this.scanner.getNextToken();
if (Arrays.binarySearch(expectedTokenTypes, this.currentToken) < 0) {
StringBuffer expectations = new StringBuffer(5);
for (int i = 0; i < expectedTokenTypes.length; i++) {
if (i > 0) {
expectations.append(',');
}
expectations.append(expectedTokenTypes[i]);
}
throw new AbortFormatting(
"unexpected token type, expecting:[" + expectations.toString() + "], actual:" +
this.currentToken);//$NON-NLS-1$//$NON-NLS-2$
}
print(this.scanner.currentPosition - this.scanner.startPosition, considerSpaceIfAny);
} catch (InvalidInputException e) {
throw new AbortFormatting(e);
}
}
public void printArrayQualifiedReference(int numberOfTokens, int sourceEnd) {
int currentTokenStartPosition = this.scanner.currentPosition;
int numberOfIdentifiers = 0;
try {
do {
printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT);
switch (this.currentToken = this.scanner.getNextToken()) {
case TerminalTokens.TokenNameEOF:
return;
case TerminalTokens.TokenNameWHITESPACE:
addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameCOMMENT_BLOCK:
case TerminalTokens.TokenNameCOMMENT_JAVADOC:
printBlockComment(false);
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameCOMMENT_LINE:
printLineComment();
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameIdentifier:
print(this.scanner.currentPosition - this.scanner.startPosition, false);
currentTokenStartPosition = this.scanner.currentPosition;
if (++numberOfIdentifiers == numberOfTokens) {
this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
return;
}
break;
case TerminalTokens.TokenNameDOT:
print(this.scanner.currentPosition - this.scanner.startPosition, false);
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameRPAREN:
currentTokenStartPosition = this.scanner.startPosition;
// $FALL-THROUGH$ - fall through default case...
default:
this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
return;
}
}
while (this.scanner.currentPosition <= sourceEnd);
} catch (InvalidInputException e) {
throw new AbortFormatting(e);
}
}
public void printQualifiedReference(int sourceEnd, boolean expectParenthesis) {
int currentTokenStartPosition = this.scanner.currentPosition;
try {
do {
printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT);
switch (this.currentToken = this.scanner.getNextToken()) {
case TerminalTokens.TokenNameEOF:
return;
case TerminalTokens.TokenNameWHITESPACE:
addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameCOMMENT_BLOCK:
case TerminalTokens.TokenNameCOMMENT_JAVADOC:
printBlockComment(false);
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameCOMMENT_LINE:
printLineComment();
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameIdentifier:
case TerminalTokens.TokenNameDOT:
print(this.scanner.currentPosition - this.scanner.startPosition, false);
currentTokenStartPosition = this.scanner.currentPosition;
break;
case TerminalTokens.TokenNameRPAREN:
if (expectParenthesis) {
currentTokenStartPosition = this.scanner.startPosition;
}
// $FALL-THROUGH$ - fall through default case...
default:
this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
return;
}
}
while (this.scanner.currentPosition <= sourceEnd);
} catch (InvalidInputException e) {
throw new AbortFormatting(e);
}
}
private void printRule(StringBuffer stringBuffer) {
// only called if this.tabLength > 0
for (int i = 0; i < this.pageWidth; i++) {
if ((i % this.tabLength) == 0) {
stringBuffer.append('+');
} else {
stringBuffer.append('-');
}
}
stringBuffer.append(this.lineSeparator);
for (int i = 0; i < (this.pageWidth / this.tabLength); i++) {
stringBuffer.append(i);
stringBuffer.append('\t');
}
}
void redoAlignment(AlignmentException e) {
if (e.relativeDepth > 0) { // if exception targets a distinct context
e.relativeDepth--; // record fact that current context got traversed
this.currentAlignment = this.currentAlignment.enclosing; // pop currentLocation
throw e; // rethrow
}
// reset scribe/scanner to restart at this given location
resetAt(this.currentAlignment.location);
this.scanner.resetTo(this.currentAlignment.location.inputOffset, this.scanner.eofPosition - 1);
// clean alignment chunkKind so it will think it is a new chunk again
this.currentAlignment.chunkKind = 0;
}
void redoMemberAlignment(AlignmentException e) {
// reset scribe/scanner to restart at this given location
resetAt(this.memberAlignment.location);
this.scanner.resetTo(this.memberAlignment.location.inputOffset, this.scanner.eofPosition - 1);
// clean alignment chunkKind so it will think it is a new chunk again
this.memberAlignment.chunkKind = 0;
}
public void reset() {
this.checkLineWrapping = true;
this.line = 0;
this.column = 1;
this.editsIndex = 0;
this.nlsTagCounter = 0;
}
private void resetAt(Location location) {
this.line = location.outputLine;
this.column = location.outputColumn;
this.indentationLevel = location.outputIndentationLevel;
this.numberOfIndentations = location.numberOfIndentations;
this.lastNumberOfNewLines = location.lastNumberOfNewLines;
this.needSpace = location.needSpace;
this.pendingSpace = location.pendingSpace;
this.editsIndex = location.editsIndex;
this.nlsTagCounter = location.nlsTagCounter;
if (this.editsIndex > 0) {
this.edits[this.editsIndex - 1] = location.textEdit;
}
this.formatter.lastLocalDeclarationSourceStart = location.lastLocalDeclarationSourceStart;
}
/** @param compilationUnitSource */
public void resetScanner(char[] compilationUnitSource) {
this.scanner.setSource(compilationUnitSource);
this.scannerEndPosition = compilationUnitSource.length;
this.scanner.resetTo(0, this.scannerEndPosition - 1);
this.edits = new OptimizedReplaceEdit[INITIAL_SIZE];
this.maxLines = this.lineEnds == null ? -1 : this.lineEnds.length - 1;
this.scanner.lineEnds = this.lineEnds;
this.scanner.linePtr = this.maxLines;
initFormatterCommentParser();
}
private void resize() {
System.arraycopy(this.edits, 0, (this.edits = new OptimizedReplaceEdit[this.editsIndex * 2]), 0, this.editsIndex);
}
private void setCommentIndentation(int commentIndentationLevel) {
if (commentIndentationLevel == 0) {
this.commentIndentation = null;
} else {
int length = COMMENT_INDENTATIONS.length;
if (commentIndentationLevel > length) {
System.arraycopy(COMMENT_INDENTATIONS, 0, COMMENT_INDENTATIONS = new String[commentIndentationLevel + 10],
0, length);
}
this.commentIndentation = COMMENT_INDENTATIONS[commentIndentationLevel - 1];
if (this.commentIndentation == null) {
this.tempBuffer.setLength(0);
for (int i = 0; i < commentIndentationLevel; i++) {
this.tempBuffer.append(' ');
}
this.commentIndentation = this.tempBuffer.toString();
COMMENT_INDENTATIONS[commentIndentationLevel - 1] = this.commentIndentation;
}
}
}
/*
* Look for the tags identified by the scanner to see whether some of them may change the status of the edition for the
* formatter. Do not return as soon as a match is found, as there may have several disabling/enabling tags in a comment, hence
* the last one will be the one really changing the formatter behavior...
*/
private void setEditsEnabled(int count) {
for (int i = 0; i < count; i++) {
if (this.disablingTag != null && CharOperation.equals(this.scanner.foundTaskTags[i], this.disablingTag)) {
this.editsEnabled = false;
}
if (this.enablingTag != null && CharOperation.equals(this.scanner.foundTaskTags[i], this.enablingTag)) {
this.editsEnabled = true;
}
}
}
void setIncludeComments(boolean on) {
if (on) {
this.formatComments |= CodeFormatter.F_INCLUDE_COMMENTS;
} else {
this.formatComments &= ~CodeFormatter.F_INCLUDE_COMMENTS;
}
}
void setHeaderComment(int position) {
this.headerEndPosition = position;
}
public void space() {
if (!this.needSpace)
return;
this.lastNumberOfNewLines = 0;
this.pendingSpace = true;
this.column++;
this.needSpace = false;
}
public String toString() {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("(page width = " + this.pageWidth + ") - (tabChar = ");//$NON-NLS-1$//$NON-NLS-2$
switch (this.tabChar) {
case DefaultCodeFormatterOptions.TAB:
stringBuffer.append("TAB");//$NON-NLS-1$
break;
case DefaultCodeFormatterOptions.SPACE:
stringBuffer.append("SPACE");//$NON-NLS-1$
break;
default:
stringBuffer.append("MIXED");//$NON-NLS-1$
}
stringBuffer
.append(") - (tabSize = " + this.tabLength + ")")//$NON-NLS-1$//$NON-NLS-2$
.append(this.lineSeparator)
.append(
"(line = " + this.line + ") - (column = " + this.column + ") - (identationLevel = " + this.indentationLevel +
")") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
.append(this.lineSeparator)
.append(
"(needSpace = " + this.needSpace + ") - (lastNumberOfNewLines = " + this.lastNumberOfNewLines +
") - (checkLineWrapping = " + this.checkLineWrapping +
")") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
.append(this.lineSeparator)
.append("==================================================================================") //$NON-NLS-1$
.append(this.lineSeparator);
if (this.tabLength > 0) {
printRule(stringBuffer);
}
return stringBuffer.toString();
}
public void unIndent() {
this.indentationLevel -= this.indentationSize;
this.numberOfIndentations--;
}
}