/******************************************************************************* * 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.jdt.internal.formatter; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; import java.util.Comparator; import java.util.Map; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.InvalidInputException; import org.eclipse.jdt.core.formatter.CodeFormatter; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.parser.Parser; import org.eclipse.jdt.internal.compiler.parser.Scanner; import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; import org.eclipse.jdt.internal.compiler.util.Util; import org.eclipse.jdt.internal.core.util.CodeSnippetParsingUtil; import org.eclipse.jdt.internal.core.util.RecordedParsingInformation; import org.eclipse.jdt.internal.formatter.align.Alignment; import org.eclipse.jdt.internal.formatter.align.AlignmentException; import org.eclipse.jdt.internal.formatter.comment.CommentFormatterUtil; import org.eclipse.jdt.internal.formatter.comment.HTMLEntity2JavaReader; import org.eclipse.jdt.internal.formatter.comment.IJavaDocTagConstants; import org.eclipse.jdt.internal.formatter.comment.Java2HTMLEntityReader; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DefaultLineTracker; import org.eclipse.jface.text.ILineTracker; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.text.edits.MalformedTreeException; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; /** * This class is responsible for dumping formatted source * @since 2.1 */ 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 IRegion[] regions; private IRegion[] 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, IRegion[] 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 IRegion[max]; int commentIndex = 0; for (int i = 0; i < max; i++) { IRegion 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 Region(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++) { IRegion 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) { IRegion lastRegion = this.adaptedRegions[0]; textRegionStart = lastRegion.getOffset(); textRegionEnd = textRegionStart + lastRegion.getLength(); } else { textRegionStart = this.adaptedRegions[0].getOffset(); IRegion 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) { 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--; } }