package org.eclipse.jdt.internal.formatter; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import org.eclipse.jdt.internal.compiler.*; | |
import org.eclipse.jdt.internal.compiler.parser.*; | |
import org.eclipse.jdt.internal.formatter.impl.*; | |
import java.util.*; | |
/** <h2>How to format a piece of code ?</h2> | |
* <ul><li>Create an instance of <code>CodeFormatter</code> | |
* <li>Use the method <code>void format(aString)</code> | |
* on this instance to format <code>aString</code>. | |
* It will return the formatted string.</ul> | |
*/ | |
public class CodeFormatter implements TerminalSymbols { | |
public FormatterOptions options; | |
/** | |
* Represents a block in the <code>constructions</code> stack. | |
*/ | |
public static final int BLOCK = TerminalSymbols.TokenNameLBRACE; | |
/** | |
* Represents a block following a control statement in the <code>constructions</code> stack. | |
*/ | |
public static final int NONINDENT_BLOCK = -100; | |
/** | |
* Contains the formatted output. | |
*/ | |
StringBuffer formattedSource; | |
/** | |
* Contains the current line.<br> | |
* Will be dumped at the next "newline" | |
*/ | |
StringBuffer currentLineBuffer; | |
/** | |
* Used during the formatting to get each token. | |
*/ | |
Scanner scanner; | |
/** | |
* Contains the tokens responsible for the current indentation level | |
* and the blocks not closed yet. | |
*/ | |
private int[] constructions; | |
/** | |
* Index in the <code>constructions</code> array. | |
*/ | |
private int constructionsCount; | |
/** | |
* Level of indentation of the current token (number of tab char put in front of it). | |
*/ | |
private int indentationLevel; | |
/** | |
* Regular level of indentation of all the lines | |
*/ | |
private int initialIndentationLevel; | |
/** | |
* Used to split a line. | |
*/ | |
Scanner splitScanner; | |
/** | |
* To remember the offset between the beginning of the line and the | |
* beginning of the comment. | |
*/ | |
int currentCommentOffset; | |
int currentLineIndentationLevel; | |
int maxLineSize = 30; | |
private boolean containsOpenCloseBraces; | |
private int indentationLevelForOpenCloseBraces; | |
/** | |
* Collections of positions to map | |
*/ | |
private int[] positionsToMap; | |
/** | |
* Collections of mapped positions | |
*/ | |
private int[] mappedPositions; | |
private int indexToMap; | |
private int indexInMap; | |
private int globalDelta; | |
private int lineDelta; | |
private int splitDelta; | |
private int beginningOfLineIndex; | |
/** | |
* Creates a new instance of Code Formatter using the FormattingOptions object | |
* given as argument | |
* @deprecated Use CodeFormatter(ConfigurableOption[]) instead | |
*/ | |
public CodeFormatter() { | |
this(null); | |
} | |
/** | |
* Creates a new instance of Code Formatter using the given settings. | |
*/ | |
public CodeFormatter(ConfigurableOption[] settings) { | |
// initialize internal state | |
constructionsCount = 0; | |
constructions = new int[10]; | |
currentLineIndentationLevel = indentationLevel = initialIndentationLevel; | |
currentCommentOffset = -1; | |
// initialize primary and secondary scanners | |
scanner = new Scanner(true,true); // regular scanner for forming lines | |
scanner.recordLineSeparator = true; // to remind of the position of the beginning of the line. | |
splitScanner = new Scanner(true,true); // secondary scanner to split long lines formed by primary scanning | |
// initialize current line buffer | |
currentLineBuffer = new StringBuffer(); | |
this.options = new FormatterOptions(settings); | |
} | |
/** | |
* Sets the behaviour of the formatter about the braces using the given flag.<br> | |
* <ul> | |
* <li>if true, the formatter add new line & indent before the opening brace. | |
* <li>if false, the formatter leaves the brace on the same line. | |
* </ul> | |
* @deprecated backward compatibility with VAJ | |
*/ | |
/** */ | |
public void addNewLineOnOpeningBrace(boolean flag) { | |
options.setNewLineBeforeOpeningBraceMode(flag); | |
} | |
/** | |
* Returns true if a lineSeparator has to be inserted before <code>operator</code> | |
* false otherwise. | |
*/ | |
private static boolean breakLineBeforeOperator(int operator) { | |
switch (operator) { | |
case TokenNameCOMMA : | |
case TokenNameSEMICOLON : | |
case TokenNameEQUAL : | |
return false; | |
default : | |
return true; | |
} | |
} | |
/** | |
* Returns the end of the source code. | |
*/ | |
private final String copyRemainingSource() { | |
char str[] = scanner.source; | |
int startPosition = scanner.startPosition; | |
int length = str.length - startPosition; | |
StringBuffer bufr = new StringBuffer(length); | |
if (startPosition < str.length) { | |
bufr.append(str, startPosition, length); | |
} | |
return (bufr.toString()); | |
} | |
/** | |
* Inserts <code>tabCount</code> tab character or their equivalent number of spaces. | |
*/ | |
private void dumpTab(int tabCount) { | |
if (options.indentWithTab) { | |
for (int j = 0; j < tabCount; j++) { | |
formattedSource.append('\t'); | |
increaseSplitDelta(1); | |
} | |
} else { | |
for (int i = 0, max = options.tabSize * tabCount; i < max; i++) { | |
formattedSource.append(' '); | |
increaseSplitDelta(1); | |
} | |
} | |
} | |
/** | |
* Dumps <code>currentLineBuffer</code> into the formatted string. | |
*/ | |
private void flushBuffer() { | |
String currentString = currentLineBuffer.toString(); | |
splitDelta = 0; | |
beginningOfLineIndex = formattedSource.length(); | |
if (options.maxLineLength!=0){ | |
if (containsOpenCloseBraces) { | |
containsOpenCloseBraces = false; | |
outputLine(currentString, | |
false, | |
indentationLevelForOpenCloseBraces, | |
0, | |
-1, | |
null, | |
0); | |
indentationLevelForOpenCloseBraces = currentLineIndentationLevel; | |
} else { | |
outputLine(currentString, | |
false, | |
currentLineIndentationLevel, | |
0, | |
-1, | |
null, | |
0); | |
} | |
} else { | |
formattedSource.append(currentString); | |
} | |
} | |
/** | |
* Formats the input string. | |
*/ | |
private void format() { | |
int token = 0; | |
int previousToken = 0; | |
int previousCompilableToken = 0; | |
int indentationOffset = 0; | |
int newLinesInWhitespace = 0; | |
// number of new lines in the previous whitespace token | |
// (used to leave blank lines before comments) | |
int pendingNewLines = 0; | |
boolean expectingOpenBrace = false; | |
boolean clearNonBlockIndents = false; | |
// true if all indentations till the 1st { (usefull after } or ;) | |
boolean pendingSpace = true; | |
boolean pendingNewlineAfterParen = false; | |
// true when a cr is to be put after a ) (in conditional statements) | |
boolean inAssignment = false; | |
boolean inArrayAssignment = false; | |
boolean inThrowsClause = false; | |
boolean inClassOrInterfaceHeader = false; | |
// openBracketCount is used to count the number of open brackets not closed yet. | |
int openBracketCount = 0; | |
int unarySignModifier = 0; | |
// openParenthesis[0] is used to count the parenthesis not belonging to a condition | |
// (eg foo();). parenthesis in for (...) are count elsewhere in the array. | |
int openParenthesisCount = 1; | |
int[] openParenthesis = new int[10]; | |
// tokenBeforeColon is used to know what token goes along with the current : | |
// it can be case or ? | |
int tokenBeforeColonCount = 0; | |
int[] tokenBeforeColon = new int[10]; | |
constructionsCount = 0; // initializes the constructions count. | |
// contains DO if in a DO..WHILE statement, UNITIALIZED otherwise. | |
int nlicsToken = 0; | |
// fix for 1FF17XY: LFCOM:ALL - Format problem on not matching } and else | |
boolean specialElse = false; | |
// OPTION (IndentationLevel): initial indentation level may be non-zero. | |
currentLineIndentationLevel += constructionsCount; | |
// An InvalidInputException exception might cause the termination of this loop. | |
try { | |
while (true) { | |
// Get the next token. Catch invalid input and output it | |
// with minimal formatting, also catch end of input and | |
// exit the loop. | |
try { | |
token = scanner.getNextToken(); | |
} catch (InvalidInputException e) { | |
if (!handleInvalidToken(e)) { | |
throw e; | |
} | |
token = 0; | |
} | |
if (token == Scanner.TokenNameEOF) | |
break; | |
/* ## MODIFYING the indentation level before generating new lines | |
and indentation in the output string | |
*/ | |
// Removes all the indentations made by statements not followed by a block | |
// except if the current token is ELSE, CATCH or if we are in a switch/case | |
if (clearNonBlockIndents && (token != Scanner.TokenNameWHITESPACE)) { | |
switch (token) { | |
case TokenNameelse : | |
if (constructionsCount > 0 && constructions[constructionsCount-1] == TokenNameelse){ | |
pendingNewLines = 1; | |
specialElse = true; | |
} | |
indentationLevel += popInclusiveUntil(TokenNameif); | |
break; | |
case TokenNamecatch : | |
indentationLevel += popInclusiveUntil(TokenNamecatch); | |
break; | |
case TokenNamefinally : | |
indentationLevel += popInclusiveUntil(TokenNamecatch); | |
break; | |
case TokenNamewhile : | |
if (nlicsToken == TokenNamedo) { | |
indentationLevel += pop(TokenNamedo); | |
break; | |
} | |
default : | |
indentationLevel += popExclusiveUntilBlockOrCase(); | |
// clear until a CASE, DEFAULT or BLOCK is encountered. | |
// Thus, the indentationLevel is correctly cleared either | |
// in a switch/case statement or in any other situation. | |
} | |
clearNonBlockIndents = false; | |
} | |
// returns to the indentation level created by the SWITCH keyword | |
// if the current token is a CASE or a DEFAULT | |
if (token == TokenNamecase || token == TokenNamedefault) { | |
indentationLevel += pop(TokenNamecase); | |
} | |
if (token == Scanner.TokenNamethrows) { | |
inThrowsClause = true; | |
} | |
if (token == Scanner.TokenNameclass || token == Scanner.TokenNameinterface) { | |
inClassOrInterfaceHeader = true; | |
} | |
/* ## APPEND newlines and indentations to the output string | |
*/ | |
// Do not add a new line between ELSE and IF, if the option elseIfOnSameLine is true. | |
// Fix for 1ETLWPZ: IVJCOM:ALL - incorrect "else if" formatting | |
if (pendingNewlineAfterParen && previousCompilableToken == TokenNameelse && token == TokenNameif && options.compactElseIfMode){ | |
pendingNewlineAfterParen = false; | |
pendingNewLines = 0; | |
indentationLevel += pop(TokenNameelse); | |
// because else if is now one single statement, | |
// the indentation level after it is increased by one and not by 2 | |
// (else = 1 indent, if = 1 indent, but else if = 1 indent, not 2). | |
} | |
// Add a newline & indent to the formatted source string if | |
// a for/if-else/while statement was scanned and there is no block | |
// following it. | |
pendingNewlineAfterParen = pendingNewlineAfterParen || (previousCompilableToken == TokenNameRPAREN && token == TokenNameLBRACE); | |
if (pendingNewlineAfterParen && token != Scanner.TokenNameWHITESPACE) { | |
pendingNewlineAfterParen = false; | |
// Do to add a newline & indent sequence if the current token is an | |
// open brace or a period or if the current token is a semi-colon and the | |
// previous token is a close paren. | |
// add a new line if a parenthesis belonging to a for() statement | |
// has been closed and the current token is not an opening brace | |
if (token != TokenNameLBRACE && !isComment(token) | |
// to avoid adding new line between else and a comment | |
&& token != TokenNameDOT | |
&& ! (previousCompilableToken == TokenNameRPAREN | |
&& token == TokenNameSEMICOLON)) { | |
newLine(1); | |
currentLineIndentationLevel += indentationLevel; | |
pendingNewLines = 0; | |
pendingSpace = false; | |
} else { | |
if (token == TokenNameLBRACE && options.newLineBeforeOpeningBraceMode) { | |
newLine(1); | |
currentLineIndentationLevel = indentationLevel; | |
pendingNewLines = 0; | |
pendingSpace = false; | |
} | |
} | |
} | |
// see PR 1G5G8EC | |
if (token == TokenNameLBRACE && inThrowsClause) { | |
inThrowsClause = false; | |
if (options.newLineBeforeOpeningBraceMode) { | |
newLine(1); | |
currentLineIndentationLevel = indentationLevel; | |
pendingNewLines = 0; | |
pendingSpace = false; | |
} | |
} | |
// see PR 1G5G82G | |
if (token == TokenNameLBRACE && inClassOrInterfaceHeader) { | |
inClassOrInterfaceHeader = false; | |
if (options.newLineBeforeOpeningBraceMode) { | |
newLine(1); | |
currentLineIndentationLevel = indentationLevel; | |
pendingNewLines = 0; | |
pendingSpace = false; | |
} | |
} | |
// Add pending new lines to the formatted source string. | |
// Note: pending new lines are not added if the current token | |
// is a single line comment or whitespace. | |
// if the comment is between parenthesis, there is no blank line preservation | |
// (if it's a one-line comment, a blank line is added after it). | |
if (((pendingNewLines > 0 && (!isComment(token))) | |
|| (newLinesInWhitespace > 0 | |
&& (openParenthesisCount <= 1 && isComment(token))) | |
|| (previousCompilableToken == TokenNameLBRACE && token == TokenNameRBRACE)) | |
&& token != Scanner.TokenNameWHITESPACE) { | |
// Do not add newline & indent between an adjoining close brace and | |
// close paren. Anonymous inner classes may use this form. | |
boolean closeBraceAndCloseParen = | |
previousToken == TokenNameRBRACE && token == TokenNameRPAREN; | |
// OPTION (NewLineInCompoundStatement): do not add newline & indent | |
// between close brace and else, (do) while, catch, and finally if | |
// newlineInCompoundStatement is true. | |
boolean nlicsOption = | |
previousToken == TokenNameRBRACE | |
&& !options.newlineInControlStatementMode | |
&& (token == TokenNameelse | |
|| (token == TokenNamewhile && nlicsToken == TokenNamedo) | |
|| token == TokenNamecatch | |
|| token == TokenNamefinally); | |
// Do not add a newline & indent between a close brace and semi-colon. | |
boolean semiColonAndCloseBrace = | |
previousToken == TokenNameRBRACE && token == TokenNameSEMICOLON; | |
// Do not add a new line & indent between a multiline comment and a opening brace | |
boolean commentAndOpenBrace = previousToken == Scanner.TokenNameCOMMENT_BLOCK && token == TokenNameLBRACE; | |
// Do not add a newline & indent between a close brace and a colon (in array assignments, for example). | |
boolean commaAndCloseBrace = | |
previousToken == TokenNameRBRACE && token == TokenNameCOMMA; | |
// Add a newline and indent, if appropriate. | |
if (specialElse || (!commentAndOpenBrace && !closeBraceAndCloseParen && !nlicsOption && !semiColonAndCloseBrace && !commaAndCloseBrace)) { | |
// if clearAllBlankLinesMode=false, leaves the blank lines | |
// inserted by the user | |
// if clearAllBlankLinesMode=true, removes all of then | |
// and insert only blank lines required by the formatting. | |
if (!options.clearAllBlankLinesMode) { | |
pendingNewLines = | |
(pendingNewLines < newLinesInWhitespace) | |
// (isComment(token)) | |
? newLinesInWhitespace | |
: pendingNewLines; | |
pendingNewLines = (pendingNewLines > 2) ? 2 : pendingNewLines; | |
} | |
if (previousCompilableToken == TokenNameLBRACE && token == TokenNameRBRACE) { | |
containsOpenCloseBraces = true; | |
indentationLevelForOpenCloseBraces = currentLineIndentationLevel; | |
if (isComment(previousToken)) { | |
newLine(pendingNewLines); | |
} else { | |
/* if (!(constructionsCount > 1 | |
&& constructions[constructionsCount-1] == NONINDENT_BLOCK | |
&& (constructions[constructionsCount-2] == TokenNamefor | |
|| constructions[constructionsCount-2] == TokenNamewhile))) {*/ | |
if (options.newLineInEmptyBlockMode) { | |
if (inArrayAssignment) { | |
newLine(1); // array assigment with an empty block | |
} else { | |
newLine(pendingNewLines); | |
} | |
} | |
// } | |
} | |
} else { | |
// see PR 1FKKC3U: LFCOM:WINNT - Format problem with a comment before the ';' | |
if (!((previousToken == Scanner.TokenNameCOMMENT_BLOCK || previousToken == Scanner.TokenNameCOMMENT_JAVADOC) && token == TokenNameSEMICOLON)) { | |
newLine(pendingNewLines); | |
} | |
} | |
if (((previousCompilableToken == TokenNameSEMICOLON) | |
|| (previousCompilableToken == TokenNameLBRACE) | |
|| (previousCompilableToken == TokenNameRBRACE) | |
|| (isComment(previousToken))) | |
&& (token == TokenNameRBRACE)) { | |
indentationOffset = -1; | |
indentationLevel += popExclusiveUntilBlock(); | |
} | |
if (previousToken == Scanner.TokenNameCOMMENT_LINE && inAssignment) { | |
// PR 1FI5IPO | |
currentLineIndentationLevel++; | |
} else { | |
currentLineIndentationLevel = indentationLevel + indentationOffset; | |
} | |
pendingSpace = false; | |
indentationOffset = 0; | |
} | |
pendingNewLines = 0; | |
newLinesInWhitespace = 0; | |
specialElse = false; | |
if (nlicsToken == TokenNamedo && token == TokenNamewhile) { | |
nlicsToken = 0; | |
} | |
} | |
switch (token) { | |
case TokenNameelse : | |
case TokenNamefinally : | |
expectingOpenBrace = true; | |
pendingNewlineAfterParen = true; | |
indentationLevel += pushControlStatement(token); | |
break; | |
case TokenNamecase : | |
case TokenNamedefault : | |
if (tokenBeforeColonCount == tokenBeforeColon.length) { | |
System.arraycopy( | |
tokenBeforeColon, | |
0, | |
(tokenBeforeColon = new int[tokenBeforeColonCount * 2]), | |
0, | |
tokenBeforeColonCount); | |
} | |
tokenBeforeColon[tokenBeforeColonCount++] = TokenNamecase; | |
indentationLevel += pushControlStatement(TokenNamecase); | |
break; | |
case TokenNameQUESTION : | |
if (tokenBeforeColonCount == tokenBeforeColon.length) { | |
System.arraycopy( | |
tokenBeforeColon, | |
0, | |
(tokenBeforeColon = new int[tokenBeforeColonCount * 2]), | |
0, | |
tokenBeforeColonCount); | |
} | |
tokenBeforeColon[tokenBeforeColonCount++] = token; | |
break; | |
case TokenNameswitch : | |
case TokenNamefor : | |
case TokenNameif : | |
case TokenNamewhile : | |
if (openParenthesisCount == openParenthesis.length) { | |
System.arraycopy( | |
openParenthesis, | |
0, | |
(openParenthesis = new int[openParenthesisCount * 2]), | |
0, | |
openParenthesisCount); | |
} | |
openParenthesis[openParenthesisCount++] = 0; | |
expectingOpenBrace = true; | |
indentationLevel += pushControlStatement(token); | |
break; | |
case TokenNametry : | |
pendingNewlineAfterParen = true; | |
case TokenNamecatch : | |
// several CATCH statements can be contiguous. | |
// a CATCH is encountered pop until first CATCH (if a CATCH follows a TRY it works the same way, | |
// as CATCH and TRY are the same token in the stack). | |
expectingOpenBrace = true; | |
indentationLevel += pushControlStatement(TokenNamecatch); | |
break; | |
case TokenNamedo : | |
expectingOpenBrace = true; | |
indentationLevel += pushControlStatement(token); | |
nlicsToken = token; | |
break; | |
case TokenNamenew : | |
// The flag inAssigment is used to properly format | |
// array assignments, and if a non-array assignment | |
// statement is in progress, it is no longer | |
// beneficial to know this, so set the flag to false. | |
/* if (!inArrayAssignment) { | |
inAssignment = false; | |
}*/ | |
break; | |
case TokenNameLPAREN : | |
// Put a space between the previous and current token if the | |
// previous token was not a keyword, open paren, logical | |
// compliment (eg: !), semi-colon, open brace, close brace, | |
// super, or this. | |
if (previousCompilableToken != TokenNameLBRACKET | |
&& previousToken != TokenNameIdentifier | |
&& previousToken != 0 | |
&& previousToken != TokenNameNOT | |
&& previousToken != TokenNameLPAREN | |
&& previousToken != TokenNameTWIDDLE | |
&& previousToken != TokenNameSEMICOLON | |
&& previousToken != TokenNameLBRACE | |
&& previousToken != TokenNameRBRACE | |
&& previousToken != TokenNamesuper | |
&& previousToken != TokenNamethis) { | |
space(); | |
} | |
// If in a for/if/while statement, increase the parenthesis count | |
// for the current openParenthesisCount | |
// else increase the count for stand alone parenthesis. | |
if (openParenthesisCount > 0) | |
openParenthesis[openParenthesisCount - 1]++; | |
else | |
openParenthesis[0]++; | |
pendingSpace = false; | |
break; | |
case TokenNameRPAREN : | |
// Decrease the parenthesis count | |
// if there is no more unclosed parenthesis, | |
// a new line and indent may be append (depending on the next token). | |
if ((openParenthesisCount > 1) | |
&& (openParenthesis[openParenthesisCount - 1] > 0)) { | |
openParenthesis[openParenthesisCount - 1]--; | |
if (openParenthesis[openParenthesisCount - 1] <= 0) { | |
pendingNewlineAfterParen = true; | |
inAssignment = false; | |
openParenthesisCount--; | |
} | |
} else { | |
openParenthesis[0]--; | |
} | |
pendingSpace = false; | |
break; | |
case TokenNameLBRACE : | |
if ((previousCompilableToken == TokenNameRBRACKET) || (previousCompilableToken == TokenNameEQUAL)) { | |
// if (previousCompilableToken == TokenNameRBRACKET) { | |
inArrayAssignment = true; | |
inAssignment = false; | |
} | |
if (inArrayAssignment) { | |
indentationLevel += pushBlock(); | |
} else { | |
// Add new line and increase indentation level after open brace. | |
pendingNewLines = 1; | |
indentationLevel += pushBlock(); | |
} | |
break; | |
case TokenNameRBRACE : | |
if (previousCompilableToken == TokenNameRPAREN) { | |
pendingSpace = false; | |
} | |
if (inArrayAssignment) { | |
inArrayAssignment = false; | |
pendingNewLines = 1; | |
indentationLevel += popInclusiveUntilBlock(); | |
} else { | |
pendingNewLines = 1; | |
indentationLevel += popInclusiveUntilBlock(); | |
if (previousCompilableToken == TokenNameRPAREN){ | |
// fix for 1FGDDV6: LFCOM:WIN98 - Weird splitting on message expression | |
currentLineBuffer.append(options.lineSeparatorSequence); | |
increaseLineDelta(options.lineSeparatorSequence.length); | |
} | |
if (constructionsCount > 0) { | |
switch (constructions[constructionsCount - 1]) { | |
case TokenNamefor : | |
//indentationLevel += popExclusiveUntilBlock(); | |
//break; | |
case TokenNameswitch : | |
case TokenNameif : | |
case TokenNameelse : | |
case TokenNametry : | |
case TokenNamecatch : | |
case TokenNamefinally : | |
case TokenNamewhile : | |
case TokenNamedo : | |
clearNonBlockIndents = true; | |
default : | |
break; | |
} | |
} | |
} | |
break; | |
case TokenNameLBRACKET : | |
openBracketCount++; | |
pendingSpace = false; | |
break; | |
case TokenNameRBRACKET : | |
openBracketCount-=(openBracketCount>0)?1:0; | |
// if there is no left bracket to close, the right bracket is ignored. | |
pendingSpace = false; | |
break; | |
case TokenNameCOMMA : | |
case TokenNameDOT : | |
pendingSpace = false; | |
break; | |
case TokenNameSEMICOLON : | |
// Do not generate line terminators in the definition of | |
// the for statement. | |
// if not in this case, jump a line and reduce indentation after the brace | |
// if the block it closes belongs to a conditional statement (if, while, do...). | |
if (openParenthesisCount <= 1) { | |
pendingNewLines = 1; | |
if (expectingOpenBrace) { | |
clearNonBlockIndents = true; | |
expectingOpenBrace = false; | |
} | |
} | |
inAssignment = false; | |
pendingSpace = false; | |
break; | |
case TokenNamePLUS_PLUS : | |
case TokenNameMINUS_MINUS : | |
// Do not put a space between a post-increment/decrement | |
// and the identifier being modified. | |
if (previousToken == TokenNameIdentifier | |
|| previousToken == TokenNameRBRACKET) { | |
pendingSpace = false; | |
} | |
break; | |
case TokenNamePLUS : // previously ADDITION | |
case TokenNameMINUS : | |
// Handle the unary operators plus and minus via a 3-state flag. | |
if (!isLiteralToken(previousToken) | |
&& previousToken != TokenNameIdentifier | |
&& previousToken != TokenNameRPAREN | |
&& previousToken != TokenNameRBRACKET) { | |
unarySignModifier = 2; | |
} | |
break; | |
case TokenNameCOLON : | |
// In a switch/case statement, add a newline & indent | |
// when a colon is encountered. | |
if (tokenBeforeColonCount > 0) { | |
if (tokenBeforeColon[tokenBeforeColonCount - 1] == TokenNamecase) { | |
pendingNewLines = 1; | |
} | |
tokenBeforeColonCount--; | |
} | |
break; | |
case TokenNameEQUAL : | |
inAssignment = true; | |
break; | |
case Scanner.TokenNameCOMMENT_LINE : | |
pendingNewLines = 1; | |
if (inAssignment) { | |
currentLineIndentationLevel++; | |
} | |
break; // a line is always inserted after a one-line comment | |
case Scanner.TokenNameCOMMENT_JAVADOC : | |
case Scanner.TokenNameCOMMENT_BLOCK : | |
currentCommentOffset = getCurrentCommentOffset(); | |
if (openParenthesis[0]==0 && openBracketCount < 1){ | |
pendingNewLines = 1; | |
// a new line is inserted only if the comment is not between parenthesis. | |
} | |
break; | |
case Scanner.TokenNameWHITESPACE : | |
// Count the number of line terminators in the whitespace so | |
// line spacing can be preserved near comments. | |
char[] source = scanner.source; | |
newLinesInWhitespace = 0; | |
for ( | |
int i = scanner.startPosition, max = scanner.currentPosition; | |
i < max; | |
i++) { | |
if (source[i] == '\n') { | |
newLinesInWhitespace++; | |
} | |
} | |
increaseLineDelta(scanner.startPosition - scanner.currentPosition); | |
break; | |
default : | |
if ((token == TokenNameIdentifier) | |
|| isLiteralToken(token) | |
|| token == TokenNamesuper | |
|| token == TokenNamethis) { | |
// Do not put a space between a unary operator | |
// (eg: ++, --, +, -) and the identifier being modified. | |
unarySignModifier -= (unarySignModifier > 0) ? 1 : 0; | |
if (previousToken == TokenNamePLUS_PLUS | |
|| previousToken == TokenNameMINUS_MINUS | |
|| (unarySignModifier > 0)) { | |
pendingSpace = false; | |
} | |
} | |
break; | |
} | |
// Do not output whitespace tokens. | |
if (token != Scanner.TokenNameWHITESPACE) { | |
/* Add pending space to the formatted source string. | |
Do not output a space under the following circumstances: | |
1) this is the first pass | |
2) previous token is an open paren | |
3) previous token is a period | |
4) previous token is the logical compliment (eg: !) | |
5) previous token is the bitwise compliment (eg: ~) | |
6) previous token is the open bracket (eg: [) | |
7) in an assignment statement, if the previous token is an | |
open brace or the current token is a close brace | |
8) previous token is a single line comment | |
*/ | |
boolean openAndCloseBrace = previousCompilableToken == TokenNameLBRACE && token == TokenNameRBRACE; | |
// to be replaced by a nicer condition. | |
if (pendingSpace | |
&& insertSpaceAfter(previousToken) | |
&& ! (inAssignment | |
&& (previousToken == TokenNameLBRACE || token == TokenNameRBRACE)) | |
&& previousToken != Scanner.TokenNameCOMMENT_LINE) { | |
if ((! (options.compactAssignmentMode && token == TokenNameEQUAL)) && !openAndCloseBrace) | |
space(); | |
} | |
// Add the next token to the formatted source string. | |
outputCurrentToken(token); | |
if (token == Scanner.TokenNameCOMMENT_LINE && openParenthesisCount > 1){ | |
pendingNewLines = 0; | |
currentLineBuffer.append(options.lineSeparatorSequence); | |
increaseLineDelta(options.lineSeparatorSequence.length); | |
} | |
pendingSpace = true; | |
} | |
// Whitespace tokens do not need to be remembered. | |
if (token != Scanner.TokenNameWHITESPACE) { | |
previousToken = token; | |
if (token != Scanner.TokenNameCOMMENT_BLOCK | |
&& token != Scanner.TokenNameCOMMENT_LINE | |
&& token != Scanner.TokenNameCOMMENT_JAVADOC) { | |
previousCompilableToken = token; | |
} | |
} | |
} | |
output(copyRemainingSource()); | |
flushBuffer(); // dump the last token of the source in the formatted output. | |
} catch (InvalidInputException e) { | |
output(copyRemainingSource()); | |
flushBuffer(); // dump the last token of the source in the formatted output. | |
} | |
} | |
/** | |
* Formats a given source string, starting indenting it at depth 0 | |
* using default options. | |
*/ | |
public static String format(String sourceString) { | |
return format(sourceString, 0, null); | |
} | |
/** | |
* Formats a given source string, starting indenting it at a particular | |
* depth and using the given options | |
*/ | |
public static String format(String sourceString, int initialIndentationLevel, ConfigurableOption[] options) { | |
CodeFormatter formatter = new CodeFormatter(options); | |
formatter.setInitialIndentationLevel(initialIndentationLevel); | |
return formatter.formatSourceString(sourceString); | |
} | |
/** | |
* Formats the char array <code>sourceString</code>, | |
* and returns a string containing the formatted version. | |
* @return the formatted ouput. | |
*/ | |
public String formatSourceString(String sourceString) { | |
char[] sourceChars = sourceString.toCharArray(); | |
formattedSource = new StringBuffer(sourceChars.length); | |
scanner.setSourceBuffer(sourceChars); | |
format(); | |
return formattedSource.toString(); | |
} | |
/** | |
* Returns the number of characters and tab char between the beginning of the line | |
* and the beginning of the comment. | |
*/ | |
private int getCurrentCommentOffset() { | |
int linePtr = scanner.linePtr; | |
// if there is no beginning of line, return 0. | |
if (linePtr < 0) | |
return 0; | |
int offset = 0; | |
int beginningOfLine = scanner.lineEnds[linePtr]; | |
int currentStartPosition = scanner.startPosition; | |
char[] source = scanner.source; | |
// find the position of the beginning of the line containing the comment | |
while (beginningOfLine > currentStartPosition) { | |
if (linePtr > 0) { | |
beginningOfLine = scanner.lineEnds[--linePtr]; | |
} else { | |
beginningOfLine = 0; | |
break; | |
} | |
} | |
for (int i = beginningOfLine ; i < currentStartPosition ; i++){ | |
char currentCharacter = source[i]; | |
switch(currentCharacter){ | |
case '\t': | |
offset += options.tabSize; | |
case '\n': | |
case '\r': | |
break; | |
default: | |
offset++; | |
} | |
} | |
return offset; | |
} | |
/** | |
* Returns an array of descriptions for the configurable options. | |
* The descriptions may be changed and passed back to a different | |
* compiler. | |
*/ | |
public static ConfigurableOption[] getDefaultOptions(Locale locale) { | |
return new FormatterOptions().getConfigurableOptions(locale); | |
} | |
/** | |
* Returns the array of mapped positions. | |
* Returns null is no positions have been set. | |
* @return int[] | |
*/ | |
public int[] getMappedPositions() { | |
return mappedPositions; | |
} | |
/** | |
* Returns the priority of the token given as argument<br> | |
* The most prioritary the token is, the smallest the return value is. | |
* @return the priority of <code>token</code> | |
* @param token the token of which the priority is requested | |
*/ | |
private static int getTokenPriority(int token) { | |
switch (token) { | |
case TokenNameextends: | |
case TokenNameimplements: | |
case TokenNamethrows: | |
return 10; | |
case TokenNameSEMICOLON : // ; | |
return 20; | |
case TokenNameCOMMA : // , | |
return 25; | |
case TokenNameEQUAL : // = | |
return 30; | |
case TokenNameAND_AND : // && | |
case TokenNameOR_OR : // || | |
return 40; | |
case TokenNameQUESTION : // ? | |
case TokenNameCOLON : // : | |
return 50; // it's better cutting on ?: than on ; | |
case TokenNameEQUAL_EQUAL : // == | |
case TokenNameNOT_EQUAL : // != | |
return 60; | |
case TokenNameLESS : // < | |
case TokenNameLESS_EQUAL : // <= | |
case TokenNameGREATER : // > | |
case TokenNameGREATER_EQUAL : // >= | |
case TokenNameinstanceof : // instanceof | |
return 70; | |
case TokenNamePLUS : // + | |
case TokenNameMINUS : // - | |
return 80; | |
case TokenNameMULTIPLY : // * | |
case TokenNameDIVIDE : // / | |
case TokenNameREMAINDER : // % | |
return 90; | |
case TokenNameLEFT_SHIFT : // << | |
case TokenNameRIGHT_SHIFT : // >> | |
case TokenNameUNSIGNED_RIGHT_SHIFT : // >>> | |
return 100; | |
case TokenNameAND : // & | |
case TokenNameOR : // | | |
case TokenNameXOR : // ^ | |
return 110; | |
case TokenNameMULTIPLY_EQUAL : // *= | |
case TokenNameDIVIDE_EQUAL : // /= | |
case TokenNameREMAINDER_EQUAL : // %= | |
case TokenNamePLUS_EQUAL : // += | |
case TokenNameMINUS_EQUAL : // -= | |
case TokenNameLEFT_SHIFT_EQUAL : // <<= | |
case TokenNameRIGHT_SHIFT_EQUAL : // >>= | |
case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>= | |
case TokenNameAND_EQUAL : // &= | |
case TokenNameXOR_EQUAL : // ^= | |
case TokenNameOR_EQUAL : // |= | |
return 120; | |
case TokenNameDOT : // . | |
return 130; | |
default : | |
return Integer.MAX_VALUE; | |
} | |
} | |
/** | |
* Handles the exception raised when an invalid token is encountered. | |
* Returns true if the exception has been handled, false otherwise. | |
*/ | |
private boolean handleInvalidToken(Exception e) { | |
if (e.getMessage().equals(Scanner.INVALID_CHARACTER_CONSTANT) | |
|| e.getMessage().equals(Scanner.INVALID_CHAR_IN_STRING) | |
|| e.getMessage().equals(Scanner.INVALID_ESCAPE)) { | |
return true; | |
} | |
return false; | |
} | |
private final void increaseGlobalDelta(int offset) { | |
globalDelta += offset; | |
} | |
private final void increaseLineDelta(int offset) { | |
lineDelta += offset; | |
} | |
private final void increaseSplitDelta(int offset) { | |
splitDelta += offset; | |
} | |
/** | |
* Returns true if a space has to be inserted after <code>operator</code> | |
* false otherwise. | |
*/ | |
private boolean insertSpaceAfter(int token) { | |
switch (token){ | |
case TokenNameLPAREN: | |
case TokenNameNOT: | |
case TokenNameTWIDDLE: | |
case TokenNameDOT : | |
case 0: // no token | |
case TokenNameLBRACKET: | |
case Scanner.TokenNameCOMMENT_LINE: | |
return false; | |
default: | |
return true; | |
} | |
} | |
/** | |
* Returns true if a space has to be inserted before <code>operator</code> | |
* false otherwise.<br> | |
* Cannot be static as it uses the code formatter options | |
* (to know if the compact assignment mode is on). | |
*/ | |
private boolean insertSpaceBefore(int token) { | |
switch (token) { | |
case TokenNameEQUAL: | |
return (!options.compactAssignmentMode); | |
default : | |
return false; | |
} | |
} | |
private static boolean isComment(int token) { | |
boolean result = | |
token == Scanner.TokenNameCOMMENT_BLOCK || | |
token == Scanner.TokenNameCOMMENT_LINE || | |
token == Scanner.TokenNameCOMMENT_JAVADOC; | |
return result; | |
} | |
private static boolean isLiteralToken(int token) { | |
boolean result = | |
token == TokenNameIntegerLiteral || | |
token == TokenNameLongLiteral || | |
token == TokenNameFloatingPointLiteral || | |
token == TokenNameDoubleLiteral || | |
token == TokenNameCharacterLiteral || | |
token == TokenNameStringLiteral; | |
return result; | |
} | |
/** | |
* If the length of <code>oneLineBuffer</code> exceeds <code>maxLineLength</code>, | |
* it is split and the result is dumped in <code>formattedSource</code> | |
* @param newLineCount the number of new lines to append | |
*/ | |
private void newLine(int newLineCount) { | |
// format current line | |
splitDelta = 0; | |
beginningOfLineIndex = formattedSource.length(); | |
String currentLine = currentLineBuffer.toString(); | |
if (containsOpenCloseBraces) { | |
containsOpenCloseBraces = false; | |
outputLine(currentLine, | |
false, | |
indentationLevelForOpenCloseBraces, | |
0, | |
-1, | |
null, | |
0); | |
indentationLevelForOpenCloseBraces = currentLineIndentationLevel; | |
} else { | |
outputLine(currentLine, | |
false, | |
currentLineIndentationLevel, | |
0, | |
-1, | |
null, | |
0); | |
} | |
// dump line break(s) | |
for (int i = 0; i < newLineCount; i++) { | |
formattedSource.append(options.lineSeparatorSequence); | |
increaseSplitDelta(options.lineSeparatorSequence.length); | |
} | |
// reset formatter for next line | |
int currentLength = currentLine.length(); | |
currentLineBuffer = new StringBuffer( | |
currentLength > maxLineSize | |
? maxLineSize = currentLength | |
: maxLineSize); | |
increaseGlobalDelta(splitDelta); | |
increaseGlobalDelta(lineDelta); | |
lineDelta = 0; | |
currentLineIndentationLevel = initialIndentationLevel; | |
} | |
private String operatorString(int operator) { | |
switch (operator) { | |
case TokenNameextends : | |
return "extends"/*nonNLS*/; | |
case TokenNameimplements : | |
return "implements"/*nonNLS*/; | |
case TokenNamethrows : | |
return "throws"/*nonNLS*/; | |
case TokenNameSEMICOLON : // ; | |
return ";"/*nonNLS*/; | |
case TokenNameCOMMA : // , | |
return ","/*nonNLS*/; | |
case TokenNameEQUAL : // = | |
return "="/*nonNLS*/; | |
case TokenNameAND_AND : // && (15.22) | |
return "&&"/*nonNLS*/; | |
case TokenNameOR_OR : // || (15.23) | |
return "||"/*nonNLS*/; | |
case TokenNameQUESTION : // ? (15.24) | |
return "?"/*nonNLS*/; | |
case TokenNameCOLON : // : (15.24) | |
return ":"/*nonNLS*/; | |
case TokenNameEQUAL_EQUAL : // == (15.20, 15.20.1, 15.20.2, 15.20.3) | |
return "=="/*nonNLS*/; | |
case TokenNameNOT_EQUAL : // != (15.20, 15.20.1, 15.20.2, 15.20.3) | |
return "!="/*nonNLS*/; | |
case TokenNameLESS : // < (15.19.1) | |
return "<"/*nonNLS*/; | |
case TokenNameLESS_EQUAL : // <= (15.19.1) | |
return "<="/*nonNLS*/; | |
case TokenNameGREATER : // > (15.19.1) | |
return ">"/*nonNLS*/; | |
case TokenNameGREATER_EQUAL : // >= (15.19.1) | |
return ">="/*nonNLS*/; | |
case TokenNameinstanceof : // instanceof | |
return "instanceof"/*nonNLS*/; | |
case TokenNamePLUS : // + (15.17, 15.17.2) | |
return "+"/*nonNLS*/; | |
case TokenNameMINUS : // - (15.17.2) | |
return "-"/*nonNLS*/; | |
case TokenNameMULTIPLY : // * (15.16.1) | |
return "*"/*nonNLS*/; | |
case TokenNameDIVIDE : // / (15.16.2) | |
return "/"/*nonNLS*/; | |
case TokenNameREMAINDER : // % (15.16.3) | |
return "%"/*nonNLS*/; | |
case TokenNameLEFT_SHIFT : // << (15.18) | |
return "<<"/*nonNLS*/; | |
case TokenNameRIGHT_SHIFT : // >> (15.18) | |
return ">>"/*nonNLS*/; | |
case TokenNameUNSIGNED_RIGHT_SHIFT : // >>> (15.18) | |
return ">>>"/*nonNLS*/; | |
case TokenNameAND : // & (15.21, 15.21.1, 15.21.2) | |
return "&"/*nonNLS*/; | |
case TokenNameOR : // | (15.21, 15.21.1, 15.21.2) | |
return "|"/*nonNLS*/; | |
case TokenNameXOR : // ^ (15.21, 15.21.1, 15.21.2) | |
return "^"/*nonNLS*/; | |
case TokenNameMULTIPLY_EQUAL : // *= (15.25.2) | |
return "*="/*nonNLS*/; | |
case TokenNameDIVIDE_EQUAL : // /= (15.25.2) | |
return "/="/*nonNLS*/; | |
case TokenNameREMAINDER_EQUAL : // %= (15.25.2) | |
return "%="/*nonNLS*/; | |
case TokenNamePLUS_EQUAL : // += (15.25.2) | |
return "+="/*nonNLS*/; | |
case TokenNameMINUS_EQUAL : // -= (15.25.2) | |
return "-="/*nonNLS*/; | |
case TokenNameLEFT_SHIFT_EQUAL : // <<= (15.25.2) | |
return "<<="/*nonNLS*/; | |
case TokenNameRIGHT_SHIFT_EQUAL : // >>= (15.25.2) | |
return ">>="/*nonNLS*/; | |
case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>= (15.25.2) | |
return ">>>="/*nonNLS*/; | |
case TokenNameAND_EQUAL : // &= (15.25.2) | |
return "&="/*nonNLS*/; | |
case TokenNameXOR_EQUAL : // ^= (15.25.2) | |
return "^="/*nonNLS*/; | |
case TokenNameOR_EQUAL : // |= (15.25.2) | |
return "|="/*nonNLS*/; | |
case TokenNameDOT : // . | |
return "."/*nonNLS*/; | |
default : | |
return ""/*nonNLS*/; | |
} | |
} | |
/** | |
* Appends <code>stringToOutput</code> to the formatted output.<br> | |
* If it contains \n, append a LINE_SEPARATOR and indent after it. | |
*/ | |
private void output(String stringToOutput) { | |
char currentCharacter; | |
for (int i = 0, max = stringToOutput.length(); i < max; i++) { | |
currentCharacter = stringToOutput.charAt(i); | |
if (currentCharacter != '\t') { | |
currentLineBuffer.append(currentCharacter); | |
} | |
} | |
updateMappedPositions(scanner.startPosition); | |
} | |
/** | |
* Appends <code>token</code> to the formatted output.<br> | |
* If it contains <code>\n</code>, append a LINE_SEPARATOR and indent after it. | |
*/ | |
private void outputCurrentToken(int token) { | |
char[] source = scanner.source; | |
int startPosition = scanner.startPosition; | |
switch(token) { | |
case Scanner.TokenNameCOMMENT_JAVADOC : | |
case Scanner.TokenNameCOMMENT_BLOCK : | |
case Scanner.TokenNameCOMMENT_LINE : | |
boolean endOfLine = false; | |
int currentCommentOffset = getCurrentCommentOffset(); | |
int beginningOfLineSpaces = 0; | |
for (int i = startPosition, max = scanner.currentPosition; i < max; i++) { | |
char currentCharacter = source[i]; | |
switch(currentCharacter) { | |
case '\r' : | |
case '\n' : | |
endOfLine = true; | |
currentLineBuffer.append(currentCharacter); | |
beginningOfLineSpaces = 0; | |
break; | |
case '\t' : | |
if (endOfLine) { | |
// we remove a maximum of currentCommentOffset characters (tabs are converted to space numbers). | |
beginningOfLineSpaces+=options.tabSize; | |
if (beginningOfLineSpaces > currentCommentOffset) | |
currentLineBuffer.append(currentCharacter); | |
} else { | |
currentLineBuffer.append(currentCharacter); | |
} | |
break; | |
case ' ' : | |
if (endOfLine) { | |
// we remove a maximum of currentCommentOffset characters (tabs are converted to space numbers). | |
beginningOfLineSpaces++; | |
if (beginningOfLineSpaces > currentCommentOffset) | |
currentLineBuffer.append(currentCharacter); | |
} else { | |
currentLineBuffer.append(currentCharacter); | |
} | |
break; | |
default: | |
beginningOfLineSpaces = 0; | |
currentLineBuffer.append(currentCharacter); | |
endOfLine = false; | |
} | |
} | |
break; | |
default: | |
currentLineBuffer.append(source, startPosition, scanner.currentPosition - startPosition); | |
} | |
updateMappedPositions(startPosition); | |
} | |
/** | |
* Outputs <code>currentString</code>:<br> | |
* <ul><li>If its length is < maxLineLength, output | |
* <li>Otherwise it is split.</ul> | |
* @param currentString string to output | |
* @param preIndented whether the string to output was pre-indented | |
* @param depth number of indentation to put in front of <code>currentString</code> | |
* @param operator value of the operator belonging to <code>currentString</code>. | |
*/ | |
private void outputLine( | |
String currentString, | |
boolean preIndented, | |
int depth, | |
int operator, | |
int substringIndex, | |
int[] startSubstringIndexes, | |
int offsetInGlobalLine) { | |
boolean emptyFirstSubString = false; | |
String operatorString = operatorString(operator); | |
boolean placeOperatorBehind = !breakLineBeforeOperator(operator); | |
boolean placeOperatorAhead = !placeOperatorBehind; | |
// dump prefix operator? | |
if (placeOperatorAhead) { | |
if (!preIndented) { | |
dumpTab(depth); | |
preIndented = true; | |
} | |
if (operator != 0) { | |
if (insertSpaceBefore(operator)) { | |
formattedSource.append(' '); | |
increaseSplitDelta(1); | |
} | |
formattedSource.append(operatorString); | |
increaseSplitDelta(operatorString.length()); | |
if (insertSpaceAfter(operator) | |
&& operator != TokenNameimplements | |
&& operator != TokenNameextends | |
&& operator != TokenNamethrows) { | |
formattedSource.append(' '); | |
increaseSplitDelta(1); | |
} | |
} | |
} | |
SplitLine splitLine = null; | |
if (options.maxLineLength == 0 | |
|| currentString.length() < options.maxLineLength | |
|| (splitLine = split(currentString, offsetInGlobalLine)) == null) { | |
// depending on the type of operator, outputs new line before of after dumping it | |
// indent before postfix operator | |
// indent also when the line cannot be split | |
if (operator == TokenNameextends | |
|| operator == TokenNameimplements | |
|| operator == TokenNamethrows) { | |
formattedSource.append(' '); | |
increaseSplitDelta(1); | |
} | |
if (placeOperatorBehind) { | |
if (!preIndented) { | |
dumpTab(depth); | |
} | |
} | |
boolean containsMultiLineComment = currentString.lastIndexOf("/*"/*nonNLS*/) != -1; | |
int numberOfSpaces = 0; | |
int max = currentString.length(); | |
updateMappedPositionsWhileSplitting( | |
beginningOfLineIndex, | |
beginningOfLineIndex + max); | |
for (int i = 0; i < max; i++) { | |
char currentChar = currentString.charAt(i); | |
switch (currentChar) { | |
case '\r' : | |
break; | |
case '\n' : | |
if (i != max - 1) { | |
// fix for 1FFYL5C: LFCOM:ALL - Incorrect indentation when split with a comment inside a condition | |
// a substring cannot end with a lineSeparatorSequence, | |
// except if it has been added by format() after a one-line comment | |
formattedSource.append(options.lineSeparatorSequence); | |
increaseSplitDelta(options.lineSeparatorSequence.length); | |
if (containsMultiLineComment) { | |
// fix for 1FGGQCN: LFCOM:ALL - Space management in comments for the formatter | |
dumpTab(currentLineIndentationLevel); | |
} else { | |
// 1FGDDV6: LFCOM:WIN98 - Weird splitting on message expression | |
dumpTab(depth - 1); | |
} | |
} | |
break; | |
default : | |
formattedSource.append(currentChar); | |
} | |
} | |
// update positions inside the mappedPositions table | |
if (substringIndex != -1) { | |
int startPosition = | |
beginningOfLineIndex + startSubstringIndexes[substringIndex]; | |
updateMappedPositionsWhileSplitting(startPosition, startPosition + max); | |
// compute the splitDelta resulting with the operator and blank removal | |
if (substringIndex + 1 != startSubstringIndexes.length) { | |
increaseSplitDelta( | |
startSubstringIndexes[substringIndex] | |
+ max | |
- startSubstringIndexes[substringIndex | |
+ 1]); | |
} | |
} | |
// dump postfix operator? | |
if (placeOperatorBehind) { | |
if (insertSpaceBefore(operator)) { | |
formattedSource.append(' '); | |
if (operator != 0) { | |
increaseSplitDelta(1); | |
} | |
} | |
formattedSource.append(operatorString); | |
if (operator != 0) { | |
increaseSplitDelta(operatorString.length()); | |
} | |
} | |
return; | |
} | |
// fix for 1FG0BA3: LFCOM:WIN98 - Weird splitting on interfaces | |
// extends has to stand alone on a line when currentString has been split. | |
if (options.maxLineLength != 0 | |
&& splitLine != null | |
&& (operator == TokenNameextends | |
|| operator == TokenNameimplements | |
|| operator == TokenNamethrows)) { | |
formattedSource.append(options.lineSeparatorSequence); | |
increaseSplitDelta(options.lineSeparatorSequence.length); | |
dumpTab(depth + 1); | |
} else { | |
if (operator == TokenNameextends | |
|| operator == TokenNameimplements | |
|| operator == TokenNamethrows) { | |
formattedSource.append(' '); | |
increaseSplitDelta(1); | |
} | |
} | |
// perform actual splitting | |
String result[] = splitLine.substrings; | |
int[] splitOperators = splitLine.operators; | |
int[] splitLineStartIndexes = splitLine.startSubstringsIndexes; | |
if (result[0].length() == 0) { | |
// when the substring 0 is null, the substring 1 is correctly indented. | |
depth--; | |
emptyFirstSubString = true; | |
} | |
// the operator going in front of the result[0] string is the operator parameter | |
for (int i = 0, max = result.length; i < max; i++) { | |
// the new depth is the current one if this is the first substring, | |
// the current one + 1 otherwise. | |
// if the substring is a comment, use the current indentation Level instead of the depth | |
// (-1 because the ouputline increases depth). | |
// (fix for 1FFC72R: LFCOM:ALL - Incorrect line split in presence of line comments) | |
String currentResult = result[i]; | |
if (currentResult.length() != 0 || splitOperators[i] != 0) { | |
int newDepth = | |
(currentResult.startsWith("/*"/*nonNLS*/) || currentResult.startsWith("//"/*nonNLS*/)) | |
? indentationLevel - 1 | |
: depth; | |
outputLine( | |
currentResult, | |
i == 0 || (i == 1 && emptyFirstSubString) ? preIndented : false, | |
i == 0 ? newDepth : newDepth + 1, | |
splitOperators[i], | |
i, | |
splitLine.startSubstringsIndexes, | |
currentString.indexOf(currentResult)); | |
if (i != max - 1) { | |
formattedSource.append(options.lineSeparatorSequence); | |
increaseSplitDelta(options.lineSeparatorSequence.length); | |
} | |
} | |
} | |
if (result.length == splitOperators.length - 1) { | |
int lastOperator = splitOperators[result.length]; | |
String lastOperatorString = operatorString(lastOperator); | |
formattedSource.append(options.lineSeparatorSequence); | |
increaseSplitDelta(options.lineSeparatorSequence.length); | |
if (breakLineBeforeOperator(lastOperator)) { | |
dumpTab(depth + 1); | |
if (lastOperator != 0) { | |
if (insertSpaceBefore(lastOperator)) { | |
formattedSource.append(' '); | |
increaseSplitDelta(1); | |
} | |
formattedSource.append(lastOperatorString); | |
increaseSplitDelta(lastOperatorString.length()); | |
if (insertSpaceAfter(lastOperator) | |
&& lastOperator != TokenNameimplements | |
&& lastOperator != TokenNameextends | |
&& lastOperator != TokenNamethrows) { | |
formattedSource.append(' '); | |
increaseSplitDelta(1); | |
} | |
} | |
} | |
} | |
if (placeOperatorBehind) { | |
if (insertSpaceBefore(operator)) { | |
formattedSource.append(' '); | |
increaseSplitDelta(1); | |
} | |
formattedSource.append(operatorString); | |
//increaseSplitDelta(operatorString.length()); | |
} | |
} | |
/** | |
* Pops the top statement of the stack if it is <code>token</code> | |
*/ | |
private int pop(int token) { | |
int delta=0; | |
if ((constructionsCount>0) && (constructions[constructionsCount-1]==token)){ | |
delta--; | |
constructionsCount--; | |
} | |
return delta; | |
} | |
/** | |
* Pops the top statement of the stack if it is a <code>BLOCK</code> or a <code>NONINDENT_BLOCK</code>. | |
*/ | |
private int popBlock() { | |
int delta=0; | |
if ((constructionsCount>0) && ((constructions[constructionsCount-1]==BLOCK) || (constructions[constructionsCount-1]==NONINDENT_BLOCK))){ | |
if (constructions[constructionsCount-1]==BLOCK) | |
delta--; | |
constructionsCount--; | |
} | |
return delta; | |
} | |
/** | |
* Pops elements until the stack is empty or the top element is <code>token</code>.<br> | |
* Does not remove <code>token</code> from the stack. | |
* @param token the token to be left as the top of the stack | |
*/ | |
private int popExclusiveUntil(int token) { | |
int delta=0; | |
int startCount=constructionsCount; | |
for (int i = startCount-1; i >= 0 && constructions[i] != token; i--){ | |
if (constructions[i]!=NONINDENT_BLOCK) | |
delta--; | |
constructionsCount--; | |
} | |
return delta; | |
} | |
/** | |
* Pops elements until the stack is empty or the top element is | |
* a <code>BLOCK</code> or a <code>NONINDENT_BLOCK</code>.<br> | |
* Does not remove it from the stack. | |
*/ | |
private int popExclusiveUntilBlock(){ | |
int startCount=constructionsCount; | |
int delta=0; | |
for (int i = startCount-1; i >= 0 && constructions[i] != BLOCK && constructions[i] != NONINDENT_BLOCK; i--){ | |
constructionsCount--; | |
delta--; | |
} | |
return delta; | |
} | |
/** | |
* Pops elements until the stack is empty or the top element is | |
* a <code>BLOCK</code>, a <code>NONINDENT_BLOCK</code> or a <code>CASE</code>.<br> | |
* Does not remove it from the stack. | |
*/ | |
private int popExclusiveUntilBlockOrCase(){ | |
int startCount=constructionsCount; | |
int delta=0; | |
for (int i = startCount-1; i >= 0 && constructions[i] != BLOCK && constructions[i] != NONINDENT_BLOCK && constructions[i]!=TokenNamecase; i--){ | |
constructionsCount--; | |
delta--; | |
} | |
return delta; | |
} | |
/** | |
* Pops elements until the stack is empty or the top element is <code>token</code>.<br> | |
* Removes <code>token</code> from the stack too. | |
* @param token the token to remove from the stack | |
*/ | |
private int popInclusiveUntil(int token) { | |
int startCount=constructionsCount; | |
int delta=0; | |
for (int i = startCount-1; i >= 0 && constructions[i] != token; i--){ | |
if (constructions[i]!=NONINDENT_BLOCK) | |
delta--; | |
constructionsCount--; | |
} | |
if (constructionsCount>0){ | |
if (constructions[constructionsCount-1]!=NONINDENT_BLOCK) | |
delta--; | |
constructionsCount--; | |
} | |
return delta; | |
} | |
/** | |
* Pops elements until the stack is empty or the top element is | |
* a <code>BLOCK</code> or a <code>NONINDENT_BLOCK</code>.<br> | |
* Does not remove it from the stack. | |
*/ | |
private int popInclusiveUntilBlock() { | |
int startCount=constructionsCount; | |
int delta=0; | |
for (int i = startCount-1; i >= 0 && (constructions[i] != BLOCK && constructions[i]!=NONINDENT_BLOCK); i--){ | |
delta--; | |
constructionsCount--; | |
} | |
if (constructionsCount>0){ | |
if (constructions[constructionsCount-1]==BLOCK) | |
delta--; | |
constructionsCount--; | |
} | |
return delta; | |
} | |
/** | |
* Pushes a block in the stack.<br> | |
* Pushes a <code>BLOCK</code> if the stack is empty or if the top element is a <code>BLOCK</code>, | |
* pushes <code>NONINDENT_BLOCK</code> otherwise. | |
* Creates a new bigger array if the current one is full. | |
*/ | |
private int pushBlock() { | |
int delta = 0; | |
if (constructionsCount == constructions.length) | |
System | |
.arraycopy(constructions, | |
0, | |
(constructions = new int[constructionsCount * 2]), | |
0, | |
constructionsCount); | |
if ((constructionsCount == 0) | |
|| (constructions[constructionsCount - 1] == BLOCK) | |
|| (constructions[constructionsCount - 1] == NONINDENT_BLOCK) | |
|| (constructions[constructionsCount - 1] == TokenNamecase)) { | |
delta++; | |
constructions[constructionsCount++] = BLOCK; | |
} | |
else { | |
constructions[constructionsCount++] = NONINDENT_BLOCK; | |
} | |
return delta; | |
} | |
/** | |
* Pushes <code>token</code>.<br> | |
* Creates a new bigger array if the current one is full. | |
*/ | |
private int pushControlStatement(int token) { | |
if (constructionsCount == constructions.length) | |
System.arraycopy(constructions, 0, (constructions = new int[constructionsCount * 2]), 0, constructionsCount); | |
constructions[constructionsCount++] = token; | |
return 1; | |
} | |
private static boolean separateFirstArgumentOn(int currentToken) { | |
//return (currentToken == TokenNameCOMMA || currentToken == TokenNameSEMICOLON); | |
return currentToken != TokenNameif | |
&& currentToken != TokenNameLPAREN | |
&& currentToken != TokenNameNOT | |
&& currentToken != TokenNamewhile | |
&& currentToken != TokenNamefor | |
&& currentToken != TokenNameswitch; | |
} | |
/** | |
* Sets the behaviour of the formatter about the braces | |
* using <coe>newBraceIndentationLevel</code><br> | |
* <ul> | |
* <li>if 0, the formatter add new line & indent | |
* when current token is open brace, | |
* remove extra indentation when current token is close brace. | |
* <li>if -1, the formatter does not add a new line before the brace.</ul> | |
* | |
* @deprecated backward compatibility with VAJ APIs, | |
* use addNewLineOnOpeningBrace(boolean) instead | |
* @see addNewLineOnOpeningBrace(boolean) | |
*/ | |
public void setBraceIndentationLevel(int newBraceIndentationLevel) { | |
options.setNewLineBeforeOpeningBraceMode(newBraceIndentationLevel==0); | |
} | |
/** | |
* Sets the behaviour of the formatter regarding the whitespaces | |
* in the string to format. | |
* @param newClearBlankLines true if the blank lines can be wiped out | |
* false otherwise. | |
* | |
* @deprecated backward compatibility with VAJ | |
*/ | |
public void setClearBlankLines(boolean newClearBlankLines) { | |
options.setClearAllBlankLinesMode(newClearBlankLines); | |
} | |
/** | |
* Sets the number of consecutive spaces used to replace the tab char | |
* if <code>newIndentationLength</code> is greater than one.<br> | |
* @param newIndentationLength 1 if 1 indent = 1 tab char, n>1 if 1 indent = n consecutive spaces. | |
* | |
* @deprecated backward compatibility with VAJ | |
*/ | |
public void setIndentationLength(int newIndentationLength) { | |
options.setTabSize(newIndentationLength); | |
} | |
/** | |
* Sets the initial indentation level | |
* @param indentationLevel new indentation level | |
* | |
* @deprecated | |
*/ | |
public void setInitialIndentationLevel(int newIndentationLevel) { | |
this.initialIndentationLevel = currentLineIndentationLevel = indentationLevel = newIndentationLevel; | |
} | |
/** | |
* Sets the behaviour of the scanner.<br> | |
* if <code>flag</code> is true, newline & indent won't be added | |
* between close brace and else, (do) while, catch, and finally. | |
* @param flag must be true if newline & indent must NOT | |
* be added between close brace and else, (do) while, | |
* catch, and finally, false otherwise. | |
* | |
* @deprecated backward compatibility with VAJ | |
*/ | |
public void setNewlineInCompoundStatement(boolean flag) { | |
options.setNewlineInControlStatementMode(flag); | |
} | |
/** | |
* Set the positions to map. The mapped positions should be retrieved using the | |
* getMappedPositions() method. | |
* @see getMappedPositions() | |
* @param positions int[] | |
*/ | |
public void setPositionsToMap(int[] positions) { | |
positionsToMap = positions; | |
lineDelta = 0; | |
globalDelta = 0; | |
mappedPositions = new int[positions.length]; | |
} | |
/** | |
* Appends a space character to the current line buffer. | |
*/ | |
private void space() { | |
currentLineBuffer.append(' '); | |
increaseLineDelta(1); | |
} | |
/** | |
* Splits <code>stringToSplit</code> on the top level token<br> | |
* If there are several identical token at the same level, | |
* the string is cut into many pieces. | |
* @return an object containing the operator and all the substrings | |
* or null if the string cannot be split | |
*/ | |
public SplitLine split(String stringToSplit) { | |
return split(stringToSplit, 0); | |
} | |
/** | |
* Splits <code>stringToSplit</code> on the top level token<br> | |
* If there are several identical token at the same level, | |
* the string is cut into many pieces. | |
* @return an object containing the operator and all the substrings | |
* or null if the string cannot be split | |
*/ | |
public SplitLine split(String stringToSplit, int offsetInGlobalLine) { | |
// local variables | |
int currentToken = 0; | |
int splitTokenType = 0; | |
int splitTokenDepth = Integer.MAX_VALUE; | |
int splitTokenPriority = Integer.MAX_VALUE; | |
int[] substringsStartPositions = new int[10]; | |
// contains the start position of substrings | |
int[] substringsEndPositions = new int[10]; | |
// contains the start position of substrings | |
int substringsCount = 1; // index in the substringsStartPosition array | |
int[] splitOperators = new int[10]; | |
// contains the start position of substrings | |
int splitOperatorsCount = 0; // index in the substringsStartPosition array | |
int[] openParenthesisPosition = new int[10]; | |
int openParenthesisPositionCount = 0; | |
int position = 0; | |
int lastOpenParenthesisPosition = -1; | |
// used to remember the position of the 1st open parenthesis | |
// needed for a pattern like: A.B(C); we want formatted like A.B( split C); | |
// setup the scanner with a new source | |
int lastCommentStartPosition = -1; | |
// to remember the start position of the last comment | |
int firstTokenOnLine = -1; | |
// to remember the first token of the line | |
int previousToken = -1; | |
// to remember the previous token. | |
splitScanner.setSourceBuffer(stringToSplit.toCharArray()); | |
try { | |
// start the loop | |
while (true) { | |
// takes the next token | |
try { | |
if (currentToken != Scanner.TokenNameWHITESPACE) | |
previousToken = currentToken; | |
currentToken = splitScanner.getNextToken(); | |
} catch (InvalidInputException e) { | |
if (!handleInvalidToken(e)) | |
throw e; | |
currentToken = 0; // this value is not modify when an exception is raised. | |
} | |
if (currentToken == TokenNameEOF) | |
break; | |
if (firstTokenOnLine == -1){ | |
firstTokenOnLine = currentToken; | |
} | |
switch (currentToken) { | |
case TokenNameRBRACE: | |
case TokenNameRPAREN : | |
if (openParenthesisPositionCount > 0) { | |
if (openParenthesisPositionCount == 1 && lastOpenParenthesisPosition < openParenthesisPosition[0]) { | |
lastOpenParenthesisPosition = openParenthesisPosition[0]; | |
} else | |
if ((splitTokenDepth == Integer.MAX_VALUE) | |
|| (splitTokenDepth > openParenthesisPositionCount | |
&& openParenthesisPositionCount == 1)) { | |
splitTokenType = 0; | |
splitTokenDepth = openParenthesisPositionCount; | |
splitTokenPriority = Integer.MAX_VALUE; | |
substringsStartPositions[0] = 0; | |
// better token means the whole line until now is the first substring | |
substringsCount = 1; // resets the count of substrings | |
substringsEndPositions[0] = openParenthesisPosition[0]; | |
// substring ends on operator start | |
position = openParenthesisPosition[0]; | |
// the string mustn't be cut before the closing parenthesis but after the opening one. | |
splitOperatorsCount = 1; // resets the count of split operators | |
splitOperators[0] = 0; | |
} | |
openParenthesisPositionCount--; | |
} | |
break; | |
case TokenNameLBRACE: | |
case TokenNameLPAREN : | |
if (openParenthesisPositionCount == openParenthesisPosition.length) { | |
System.arraycopy( | |
openParenthesisPosition, | |
0, | |
(openParenthesisPosition = new int[openParenthesisPositionCount * 2]), | |
0, | |
openParenthesisPositionCount); | |
} | |
openParenthesisPosition[openParenthesisPositionCount++] = splitScanner.currentPosition; | |
if (currentToken == TokenNameLPAREN && previousToken == TokenNameRPAREN){ | |
openParenthesisPosition[openParenthesisPositionCount - 1] = splitScanner.startPosition; | |
} | |
break; | |
case TokenNameSEMICOLON : // ; | |
case TokenNameCOMMA : // , | |
case TokenNameEQUAL : // = | |
if (openParenthesisPositionCount < splitTokenDepth | |
|| (openParenthesisPositionCount == splitTokenDepth | |
&& splitTokenPriority > getTokenPriority(currentToken))) { | |
// the current token is better than the one we currently have | |
// (in level or in priority if same level) | |
// reset the substringsCount | |
splitTokenDepth = openParenthesisPositionCount; | |
splitTokenType = currentToken; | |
splitTokenPriority = getTokenPriority(currentToken); | |
substringsStartPositions[0] = 0; | |
// better token means the whole line until now is the first substring | |
if (separateFirstArgumentOn(firstTokenOnLine) | |
&& openParenthesisPositionCount > 0) { | |
substringsCount = 2; // resets the count of substrings | |
substringsEndPositions[0] = openParenthesisPosition[splitTokenDepth - 1]; | |
substringsStartPositions[1] = openParenthesisPosition[splitTokenDepth - 1]; | |
substringsEndPositions[1] = splitScanner.startPosition; | |
splitOperatorsCount = 2; // resets the count of split operators | |
splitOperators[0] = 0; | |
splitOperators[1] = currentToken; | |
position = splitScanner.currentPosition; | |
// next substring will start from operator end | |
} else { | |
substringsCount = 1; // resets the count of substrings | |
substringsEndPositions[0] = splitScanner.startPosition; | |
// substring ends on operator start | |
position = splitScanner.currentPosition; | |
// next substring will start from operator end | |
splitOperatorsCount = 1; // resets the count of split operators | |
splitOperators[0] = currentToken; | |
} | |
} else { | |
if ((openParenthesisPositionCount == splitTokenDepth | |
&& splitTokenPriority == getTokenPriority(currentToken)) | |
&& splitTokenType!=TokenNameEQUAL && currentToken != TokenNameEQUAL) { | |
// fix for 1FG0BCN: LFCOM:WIN98 - Missing one indentation after split | |
// take only the 1st = into account. | |
// if another token with the same priority is found, | |
// push the start position of the substring and | |
// push the token into the stack. | |
// create a new array object if the current one is full. | |
if (substringsCount == substringsStartPositions.length) { | |
System.arraycopy( | |
substringsStartPositions, | |
0, | |
(substringsStartPositions = new int[substringsCount * 2]), | |
0, | |
substringsCount); | |
System.arraycopy( | |
substringsEndPositions, | |
0, | |
(substringsEndPositions = new int[substringsCount * 2]), | |
0, | |
substringsCount); | |
} | |
if (splitOperatorsCount == splitOperators.length) { | |
System.arraycopy( | |
splitOperators, | |
0, | |
(splitOperators = new int[splitOperatorsCount * 2]), | |
0, | |
splitOperatorsCount); | |
} | |
substringsStartPositions[substringsCount] = position; | |
substringsEndPositions[substringsCount++] = splitScanner.startPosition; | |
// substring ends on operator start | |
position = splitScanner.currentPosition; | |
// next substring will start from operator end | |
splitOperators[splitOperatorsCount++] = currentToken; | |
} | |
} | |
break; | |
case TokenNameCOLON : // : (15.24) | |
// see 1FK7C5R, we only split on a colon, when it is associated with a question-mark. | |
// indeed it might appear also behind a case statement, and we do not to break at this point. | |
if ((splitOperatorsCount == 0) || splitOperators[splitOperatorsCount-1] != TokenNameQUESTION){ | |
break; | |
} | |
case TokenNameextends : | |
case TokenNameimplements : | |
case TokenNamethrows : | |
case TokenNameDOT : // . | |
case TokenNameMULTIPLY : // * (15.16.1) | |
case TokenNameDIVIDE : // / (15.16.2) | |
case TokenNameREMAINDER : // % (15.16.3) | |
case TokenNamePLUS : // + (15.17, 15.17.2) | |
case TokenNameMINUS : // - (15.17.2) | |
case TokenNameLEFT_SHIFT : // << (15.18) | |
case TokenNameRIGHT_SHIFT : // >> (15.18) | |
case TokenNameUNSIGNED_RIGHT_SHIFT : // >>> (15.18) | |
case TokenNameLESS : // < (15.19.1) | |
case TokenNameLESS_EQUAL : // <= (15.19.1) | |
case TokenNameGREATER : // > (15.19.1) | |
case TokenNameGREATER_EQUAL : // >= (15.19.1) | |
case TokenNameinstanceof : // instanceof | |
case TokenNameEQUAL_EQUAL : // == (15.20, 15.20.1, 15.20.2, 15.20.3) | |
case TokenNameNOT_EQUAL : // != (15.20, 15.20.1, 15.20.2, 15.20.3) | |
case TokenNameAND : // & (15.21, 15.21.1, 15.21.2) | |
case TokenNameOR : // | (15.21, 15.21.1, 15.21.2) | |
case TokenNameXOR : // ^ (15.21, 15.21.1, 15.21.2) | |
case TokenNameAND_AND : // && (15.22) | |
case TokenNameOR_OR : // || (15.23) | |
case TokenNameQUESTION : // ? (15.24) | |
case TokenNameMULTIPLY_EQUAL : // *= (15.25.2) | |
case TokenNameDIVIDE_EQUAL : // /= (15.25.2) | |
case TokenNameREMAINDER_EQUAL : // %= (15.25.2) | |
case TokenNamePLUS_EQUAL : // += (15.25.2) | |
case TokenNameMINUS_EQUAL : // -= (15.25.2) | |
case TokenNameLEFT_SHIFT_EQUAL : // <<= (15.25.2) | |
case TokenNameRIGHT_SHIFT_EQUAL : // >>= (15.25.2) | |
case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>= (15.25.2) | |
case TokenNameAND_EQUAL : // &= (15.25.2) | |
case TokenNameXOR_EQUAL : // ^= (15.25.2) | |
case TokenNameOR_EQUAL : // |= (15.25.2) | |
if ((openParenthesisPositionCount < splitTokenDepth | |
|| (openParenthesisPositionCount == splitTokenDepth | |
&& splitTokenPriority > getTokenPriority(currentToken))) | |
&& | |
!((currentToken == TokenNamePLUS || currentToken == TokenNameMINUS) | |
&& (previousToken == TokenNameLBRACE || previousToken == TokenNameLBRACKET || splitScanner.startPosition == 0))){ | |
// the current token is better than the one we currently have | |
// (in level or in priority if same level) | |
// reset the substringsCount | |
splitTokenDepth = openParenthesisPositionCount; | |
splitTokenType = currentToken; | |
splitTokenPriority = getTokenPriority(currentToken); | |
substringsStartPositions[0] = 0; | |
// better token means the whole line until now is the first substring | |
if (separateFirstArgumentOn(firstTokenOnLine) | |
&& openParenthesisPositionCount > 0) { | |
substringsCount = 2; // resets the count of substrings | |
substringsEndPositions[0] = openParenthesisPosition[splitTokenDepth - 1]; | |
substringsStartPositions[1] = openParenthesisPosition[splitTokenDepth - 1]; | |
substringsEndPositions[1] = splitScanner.startPosition; | |
splitOperatorsCount = 3; // resets the count of split operators | |
splitOperators[0] = 0; | |
splitOperators[1] = 0; | |
splitOperators[2] = currentToken; | |
position = splitScanner.currentPosition; | |
// next substring will start from operator end | |
} else { | |
substringsCount = 1; // resets the count of substrings | |
substringsEndPositions[0] = splitScanner.startPosition; | |
// substring ends on operator start | |
position = splitScanner.currentPosition; | |
// next substring will start from operator end | |
splitOperatorsCount = 2; // resets the count of split operators | |
splitOperators[0] = 0; | |
// nothing for first operand since operator will be inserted in front of the second operand | |
splitOperators[1] = currentToken; | |
} | |
} else { | |
if (openParenthesisPositionCount == splitTokenDepth | |
&& splitTokenPriority == getTokenPriority(currentToken)) { | |
// if another token with the same priority is found, | |
// push the start position of the substring and | |
// push the token into the stack. | |
// create a new array object if the current one is full. | |
if (substringsCount == substringsStartPositions.length) { | |
System.arraycopy( | |
substringsStartPositions, | |
0, | |
(substringsStartPositions = new int[substringsCount * 2]), | |
0, | |
substringsCount); | |
System.arraycopy( | |
substringsEndPositions, | |
0, | |
(substringsEndPositions = new int[substringsCount * 2]), | |
0, | |
substringsCount); | |
} | |
if (splitOperatorsCount == splitOperators.length) { | |
System.arraycopy( | |
splitOperators, | |
0, | |
(splitOperators = new int[splitOperatorsCount * 2]), | |
0, | |
splitOperatorsCount); | |
} | |
substringsStartPositions[substringsCount] = position; | |
substringsEndPositions[substringsCount++] = splitScanner.startPosition; | |
// substring ends on operator start | |
position = splitScanner.currentPosition; | |
// next substring will start from operator end | |
splitOperators[splitOperatorsCount++] = currentToken; | |
} | |
} | |
default : | |
break; | |
} | |
if (isComment(currentToken)) { | |
lastCommentStartPosition = splitScanner.startPosition; | |
} else { | |
lastCommentStartPosition = -1; | |
} | |
} | |
} catch (InvalidInputException e) { | |
return null; | |
} | |
// if the string cannot be split, return null. | |
if (splitOperatorsCount == 0) | |
return null; | |
// ## SPECIAL CASES BEGIN | |
if (((splitOperatorsCount == 2 && splitOperators[1] == TokenNameDOT && splitTokenDepth == 0 && lastOpenParenthesisPosition > -1) | |
|| (splitOperatorsCount > 2 && splitOperators[1] == TokenNameDOT && splitTokenDepth == 0 && lastOpenParenthesisPosition > -1 && lastOpenParenthesisPosition <= options.maxLineLength) | |
|| (separateFirstArgumentOn(firstTokenOnLine) && splitTokenDepth > 0 && lastOpenParenthesisPosition > -1)) | |
&& (lastOpenParenthesisPosition<splitScanner.source.length && splitScanner.source[lastOpenParenthesisPosition]!=')')){ | |
// fix for 1FH4J2H: LFCOM:WINNT - Formatter - Empty parenthesis should not be broken on two lines | |
// only one split on a top level . | |
// or more than one split on . and substring before open parenthesis fits one line. | |
// or split inside parenthesis and first token is not a for/while/if | |
SplitLine sl = split(stringToSplit.substring(lastOpenParenthesisPosition), lastOpenParenthesisPosition); | |
if (sl == null || sl.operators[0] != TokenNameCOMMA) { | |
// trim() is used to remove the extra blanks at the end of the substring. See PR 1FGYPI1 | |
return new SplitLine( | |
new int[] {0, 0}, | |
new String[] { | |
stringToSplit.substring(0, lastOpenParenthesisPosition).trim(), | |
stringToSplit.substring(lastOpenParenthesisPosition)}, | |
new int[] {offsetInGlobalLine, lastOpenParenthesisPosition + offsetInGlobalLine}); | |
} else { | |
// right substring can be split and is split on comma | |
// copy substrings and operators | |
// except if the 1st string is empty. | |
int startIndex = (sl.substrings[0].length()==0)?1:0; | |
int subStringsLength = sl.substrings.length + 1 - startIndex; | |
String[] result = new String[subStringsLength]; | |
int[] startIndexes = new int[subStringsLength]; | |
int operatorsLength = sl.operators.length + 1 - startIndex; | |
int[] operators = new int[operatorsLength]; | |
result[0] = stringToSplit.substring(0, lastOpenParenthesisPosition); | |
operators[0] = 0; | |
System.arraycopy(sl.startSubstringsIndexes, startIndex, startIndexes, 1, subStringsLength - 1); | |
for (int i = subStringsLength - 1; i >= 0; i--) { | |
startIndexes[i] += offsetInGlobalLine; | |
} | |
System.arraycopy(sl.substrings, startIndex, result, 1, subStringsLength - 1); | |
System.arraycopy(sl.operators, startIndex, operators, 1, operatorsLength - 1); | |
return new SplitLine(operators, result, startIndexes); | |
} | |
} | |
// if the last token is a comment and the substring before the comment fits on a line, | |
// split before the comment and return the result. | |
if (lastCommentStartPosition > -1 | |
&& lastCommentStartPosition < options.maxLineLength | |
&& splitTokenPriority > 50) { | |
int end = lastCommentStartPosition; | |
int start = lastCommentStartPosition; | |
if (stringToSplit.charAt(end - 1) == ' ') { | |
end--; | |
} | |
if (start != end && stringToSplit.charAt(start) == ' ') { | |
start++; | |
} | |
return new SplitLine( | |
new int[] {0, 0}, | |
new String[] { | |
stringToSplit.substring(0, end), | |
stringToSplit.substring(start)}, | |
new int[] {0, start}); | |
} | |
if (position != stringToSplit.length()) { | |
if (substringsCount == substringsStartPositions.length) { | |
System.arraycopy( | |
substringsStartPositions, | |
0, | |
(substringsStartPositions = new int[substringsCount * 2]), | |
0, | |
substringsCount); | |
System.arraycopy( | |
substringsEndPositions, | |
0, | |
(substringsEndPositions = new int[substringsCount * 2]), | |
0, | |
substringsCount); | |
} | |
// avoid empty extra substring, e.g. line terminated with a semi-colon | |
substringsStartPositions[substringsCount] = position; | |
substringsEndPositions[substringsCount++] = stringToSplit.length(); | |
} | |
if (splitOperatorsCount == splitOperators.length) { | |
System.arraycopy( | |
splitOperators, | |
0, | |
(splitOperators = new int[splitOperatorsCount * 2]), | |
0, | |
splitOperatorsCount); | |
} | |
splitOperators[splitOperatorsCount] = 0; | |
// the last element of the stack is the position of the end of StringToSPlit | |
// +1 because the substring method excludes the last character | |
String[] result = new String[substringsCount]; | |
for (int i = 0; i < substringsCount; i++) { | |
int start = substringsStartPositions[i]; | |
int end = substringsEndPositions[i]; | |
if (stringToSplit.charAt(start) == ' ') { | |
start++; | |
substringsStartPositions[i]++; | |
} | |
if (end != start && stringToSplit.charAt(end - 1) == ' ') { | |
end--; | |
} | |
result[i] = stringToSplit.substring(start, end); | |
substringsStartPositions[i] += offsetInGlobalLine; | |
} | |
if (splitOperatorsCount > substringsCount) { | |
System.arraycopy(substringsStartPositions, 0, (substringsStartPositions = new int[splitOperatorsCount]), 0, substringsCount); | |
System.arraycopy(substringsEndPositions, 0, (substringsEndPositions = new int[splitOperatorsCount]), 0, substringsCount); | |
for (int i = substringsCount; i < splitOperatorsCount; i++) { | |
substringsStartPositions[i] = position; | |
substringsEndPositions[i] = position; | |
} | |
System.arraycopy(splitOperators, 0, (splitOperators = new int[splitOperatorsCount]), 0, splitOperatorsCount); | |
} else { | |
System.arraycopy(substringsStartPositions, 0, (substringsStartPositions = new int[substringsCount]), 0, substringsCount); | |
System.arraycopy(substringsEndPositions, 0, (substringsEndPositions = new int[substringsCount]), 0, substringsCount); | |
System.arraycopy(splitOperators, 0, (splitOperators = new int[substringsCount]), 0, substringsCount); | |
} | |
SplitLine splitLine = new SplitLine(splitOperators, result, substringsStartPositions); | |
return splitLine; | |
} | |
private void updateMappedPositions(int startPosition) { | |
if (positionsToMap == null) return; | |
char[] source = scanner.source; | |
int sourceLength = source.length; | |
while (indexToMap < positionsToMap.length && positionsToMap[indexToMap] <= startPosition) { | |
int posToMap = positionsToMap[indexToMap]; | |
if (posToMap < 0 || posToMap >= sourceLength){ // protection against out of bounds position | |
indexToMap = positionsToMap.length; // no more mapping | |
return; | |
} | |
if (Character.isWhitespace(source[posToMap])) { | |
mappedPositions[indexToMap] = startPosition + globalDelta + lineDelta; | |
} else { | |
mappedPositions[indexToMap] = posToMap + globalDelta + lineDelta; | |
} | |
indexToMap++; | |
} | |
} | |
private void updateMappedPositionsWhileSplitting(int startPosition, int endPosition) { | |
if (mappedPositions == null || mappedPositions.length == indexInMap) return; | |
while(indexInMap < mappedPositions.length | |
&& startPosition <= mappedPositions[indexInMap] | |
&& mappedPositions[indexInMap] < endPosition | |
&& indexInMap < indexToMap) { | |
mappedPositions[indexInMap]+= splitDelta; | |
indexInMap++; | |
} | |
} | |
} |