misc
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / formatter / CodeFormatter.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v0.5 
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v05.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  ******************************************************************************/
11 package net.sourceforge.phpdt.internal.formatter;
12
13 import java.io.BufferedReader;
14 import java.io.IOException;
15 import java.io.StringReader;
16 import java.util.Hashtable;
17 import java.util.Locale;
18 import java.util.Map;
19
20 import net.sourceforge.phpdt.core.ICodeFormatter;
21 import net.sourceforge.phpdt.core.compiler.CharOperation;
22 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
23 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
24 import net.sourceforge.phpdt.internal.compiler.ConfigurableOption;
25 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
26
27 import net.sourceforge.phpdt.internal.formatter.impl.FormatterOptions;
28 import net.sourceforge.phpdt.internal.formatter.impl.SplitLine;
29
30 /** <h2>How to format a piece of code ?</h2>
31  * <ul><li>Create an instance of <code>CodeFormatter</code>
32  * <li>Use the method <code>void format(aString)</code>
33  * on this instance to format <code>aString</code>.
34  * It will return the formatted string.</ul>
35 */
36 public class CodeFormatter implements ITerminalSymbols, ICodeFormatter {
37
38   public FormatterOptions options;
39
40   /** 
41    * Represents a block in the <code>constructions</code> stack.
42    */
43   public static final int BLOCK = ITerminalSymbols.TokenNameLBRACE;
44
45   /** 
46    * Represents a block following a control statement in the <code>constructions</code> stack.
47    */
48   public static final int NONINDENT_BLOCK = -100;
49
50   /** 
51    * Contains the formatted output.
52    */
53   StringBuffer formattedSource;
54
55   /** 
56    * Contains the current line.<br>
57    * Will be dumped at the next "newline"
58    */
59   StringBuffer currentLineBuffer;
60
61   /** 
62    * Used during the formatting to get each token.
63    */
64   Scanner scanner;
65
66   /** 
67    * Contains the tokens responsible for the current indentation level
68    * and the blocks not closed yet.
69    */
70   private int[] constructions;
71
72   /** 
73    * Index in the <code>constructions</code> array.
74    */
75   private int constructionsCount;
76
77   /** 
78    * Level of indentation of the current token (number of tab char put in front of it).
79    */
80   private int indentationLevel;
81
82   /** 
83    * Regular level of indentation of all the lines
84    */
85   private int initialIndentationLevel;
86
87   /** 
88    * Used to split a line.
89    */
90   Scanner splitScanner;
91
92   /** 
93    * To remember the offset between the beginning of the line and the
94    * beginning of the comment.
95    */
96   int currentCommentOffset;
97   int currentLineIndentationLevel;
98   int maxLineSize = 30;
99   private boolean containsOpenCloseBraces;
100   private int indentationLevelForOpenCloseBraces;
101
102   /**
103    * Collections of positions to map
104    */
105   private int[] positionsToMap;
106
107   /**
108    * Collections of mapped positions
109    */
110   private int[] mappedPositions;
111
112   private int indexToMap;
113
114   private int indexInMap;
115
116   private int globalDelta;
117
118   private int lineDelta;
119
120   private int splitDelta;
121
122   private int beginningOfLineIndex;
123
124   private int multipleLineCommentCounter;
125
126   /** 
127    * Creates a new instance of Code Formatter using the given settings.
128    * 
129    * @deprecated backport 1.0 internal functionality
130    */
131   public CodeFormatter(ConfigurableOption[] settings) {
132     this(convertConfigurableOptions(settings));
133   }
134
135   /** 
136    * Creates a new instance of Code Formatter using the FormattingOptions object
137    * given as argument
138    * @deprecated Use CodeFormatter(ConfigurableOption[]) instead
139    */
140   public CodeFormatter() {
141     this((Map) null);
142   }
143   /** 
144    * Creates a new instance of Code Formatter using the given settings.
145    */
146   public CodeFormatter(Map settings) {
147
148     // initialize internal state
149     constructionsCount = 0;
150     constructions = new int[10];
151     currentLineIndentationLevel = indentationLevel = initialIndentationLevel;
152     currentCommentOffset = -1;
153
154     // initialize primary and secondary scanners
155     scanner = new Scanner(true /*comment*/
156     , true /*whitespace*/
157     , false /*nls*/
158     , false /*assert*/
159     ); // regular scanner for forming lines
160     scanner.recordLineSeparator = true;
161
162     // to remind of the position of the beginning of the line.
163     splitScanner = new Scanner(true /*comment*/
164     , true /*whitespace*/
165     , false /*nls*/
166     , false /*assert*/
167     );
168     // secondary scanner to split long lines formed by primary scanning
169
170     // initialize current line buffer
171     currentLineBuffer = new StringBuffer();
172     this.options = new FormatterOptions(settings);
173   }
174
175   /**
176    * Returns true if a lineSeparator has to be inserted before <code>operator</code>
177    * false otherwise.
178    */
179   private static boolean breakLineBeforeOperator(int operator) {
180     switch (operator) {
181       case TokenNameCOMMA :
182       case TokenNameSEMICOLON :
183       case TokenNameEQUAL :
184         return false;
185       default :
186         return true;
187     }
188   }
189
190   /** 
191   * @deprecated backport 1.0 internal functionality
192   */
193   private static Map convertConfigurableOptions(ConfigurableOption[] settings) {
194     Hashtable options = new Hashtable(10);
195
196     for (int i = 0; i < settings.length; i++) {
197       if (settings[i].getComponentName().equals(CodeFormatter.class.getName())) {
198         String optionName = settings[i].getOptionName();
199         int valueIndex = settings[i].getCurrentValueIndex();
200
201         if (optionName.equals("newline.openingBrace")) { //$NON-NLS-1$
202           options.put("net.sourceforge.phpdt.core.formatter.newline.openingBrace", valueIndex == 0 ? "insert" : "do not insert"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
203
204         } else if (optionName.equals("newline.controlStatement")) { //$NON-NLS-1$
205           options.put("net.sourceforge.phpdt.core.formatter.newline.controlStatement", valueIndex == 0 ? "insert" : "do not insert"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
206
207         } else if (optionName.equals("newline.clearAll")) { //$NON-NLS-1$
208           options.put("net.sourceforge.phpdt.core.formatter.newline.clearAll", valueIndex == 0 ? "clear all" : "preserve one"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
209
210         } else if (optionName.equals("newline.elseIf")) { //$NON-NLS-1$
211           options.put("net.sourceforge.phpdt.core.formatter.newline.elseIf", valueIndex == 0 ? "do not insert" : "insert"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
212
213         } else if (optionName.equals("newline.emptyBlock")) { //$NON-NLS-1$
214           options.put("net.sourceforge.phpdt.core.formatter.newline.emptyBlock", valueIndex == 0 ? "insert" : "do not insert"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
215
216         } else if (optionName.equals("lineSplit")) { //$NON-NLS-1$
217           options.put("net.sourceforge.phpdt.core.formatter.lineSplit", String.valueOf(valueIndex)); //$NON-NLS-1$ //$NON-NLS-2$
218
219         } else if (optionName.equals("style.assignment")) { //$NON-NLS-1$
220           options.put("net.sourceforge.phpdt.core.formatter.style.assignment", valueIndex == 0 ? "compact" : "normal"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
221
222         } else if (optionName.equals("tabulation.char")) { //$NON-NLS-1$
223           options.put("net.sourceforge.phpdt.core.formatter.tabulation.char", valueIndex == 0 ? "tab" : "space"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
224
225         } else if (optionName.equals("tabulation.size")) { //$NON-NLS-1$
226           options.put("net.sourceforge.phpdt.core.formatter.tabulation.size", String.valueOf(valueIndex)); //$NON-NLS-1$ //$NON-NLS-2$
227         }
228       }
229     }
230
231     return options;
232   }
233
234   /** 
235    * Returns the end of the source code.
236    */
237   private final String copyRemainingSource() {
238     char str[] = scanner.source;
239     int startPosition = scanner.startPosition;
240     int length = str.length - startPosition;
241     StringBuffer bufr = new StringBuffer(length);
242     if (startPosition < str.length) {
243       bufr.append(str, startPosition, length);
244     }
245     return (bufr.toString());
246   }
247
248   /**
249    * Inserts <code>tabCount</code> tab character or their equivalent number of spaces.
250    */
251   private void dumpTab(int tabCount) {
252     if (options.indentWithTab) {
253       for (int j = 0; j < tabCount; j++) {
254         formattedSource.append('\t');
255         increaseSplitDelta(1);
256       }
257     } else {
258       for (int i = 0, max = options.tabSize * tabCount; i < max; i++) {
259         formattedSource.append(' ');
260         increaseSplitDelta(1);
261       }
262     }
263   }
264
265   /**
266    * Dumps <code>currentLineBuffer</code> into the formatted string.
267    */
268   private void flushBuffer() {
269     String currentString = currentLineBuffer.toString();
270     splitDelta = 0;
271     beginningOfLineIndex = formattedSource.length();
272     if (containsOpenCloseBraces) {
273       containsOpenCloseBraces = false;
274       outputLine(currentString, false, indentationLevelForOpenCloseBraces, 0, -1, null, 0);
275       indentationLevelForOpenCloseBraces = currentLineIndentationLevel;
276     } else {
277       outputLine(currentString, false, currentLineIndentationLevel, 0, -1, null, 0);
278     }
279     int scannerSourceLength = scanner.source.length;
280     if (scannerSourceLength > 2) {
281       if (scanner.source[scannerSourceLength - 1] == '\n' && scanner.source[scannerSourceLength - 2] == '\r') {
282         formattedSource.append(options.lineSeparatorSequence);
283         increaseGlobalDelta(options.lineSeparatorSequence.length - 2);
284       } else if (scanner.source[scannerSourceLength - 1] == '\n') {
285         formattedSource.append(options.lineSeparatorSequence);
286         increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
287       } else if (scanner.source[scannerSourceLength - 1] == '\r') {
288         formattedSource.append(options.lineSeparatorSequence);
289         increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
290       }
291     }
292     updateMappedPositions(scanner.startPosition);
293   }
294
295   /** 
296    * Formats the input string.
297    */
298   private void format() {
299     int token = 0;
300     int previousToken = 0;
301     int previousCompilableToken = 0;
302     int indentationOffset = 0;
303     int newLinesInWhitespace = 0;
304
305     // number of new lines in the previous whitespace token
306     // (used to leave blank lines before comments)
307     int pendingNewLines = 0;
308     boolean expectingOpenBrace = false;
309     boolean clearNonBlockIndents = false;
310     // true if all indentations till the 1st { (usefull after } or ;)
311     boolean pendingSpace = true;
312     boolean pendingNewlineAfterParen = false;
313     // true when a cr is to be put after a ) (in conditional statements)
314     boolean inAssignment = false;
315     boolean inArrayAssignment = false;
316     boolean inThrowsClause = false;
317     boolean inClassOrInterfaceHeader = false;
318
319     // openBracketCount is used to count the number of open brackets not closed yet.
320     int openBracketCount = 0;
321     int unarySignModifier = 0;
322
323     // openParenthesis[0] is used to count the parenthesis not belonging to a condition
324     // (eg foo();). parenthesis in for (...) are count elsewhere in the array.
325     int openParenthesisCount = 1;
326     int[] openParenthesis = new int[10];
327
328     // tokenBeforeColon is used to know what token goes along with the current :
329     // it can be case or ?
330     int tokenBeforeColonCount = 0;
331     int[] tokenBeforeColon = new int[10];
332
333     constructionsCount = 0; // initializes the constructions count.
334
335     // contains DO if in a DO..WHILE statement, UNITIALIZED otherwise.
336     int nlicsToken = 0;
337
338     // fix for 1FF17XY: LFCOM:ALL - Format problem on not matching } and else 
339     boolean specialElse = false;
340
341     // OPTION (IndentationLevel): initial indentation level may be non-zero.
342     currentLineIndentationLevel += constructionsCount;
343
344     // An InvalidInputException exception might cause the termination of this loop.
345     try {
346       while (true) {
347         // Get the next token.  Catch invalid input and output it
348         // with minimal formatting, also catch end of input and
349         // exit the loop.
350         try {
351           token = scanner.getNextToken();
352
353           // Patch for line comment
354           // See PR http://dev.eclipse.org/bugs/show_bug.cgi?id=23096
355           if (token == ITerminalSymbols.TokenNameCOMMENT_LINE) {
356             int length = scanner.currentPosition;
357             loop : for (int index = length - 1; index >= 0; index--) {
358               switch (scanner.source[index]) {
359                 case '\r' :
360                 case '\n' :
361                   scanner.currentPosition--;
362                   break;
363                 default :
364                   break loop;
365               }
366             }
367           }
368         } catch (InvalidInputException e) {
369           if (!handleInvalidToken(e)) {
370             throw e;
371           }
372           token = 0;
373         }
374         if (token == Scanner.TokenNameEOF)
375           break;
376
377         /* ## MODIFYING the indentation level before generating new lines
378         and indentation in the output string
379         */
380
381         // Removes all the indentations made by statements not followed by a block
382         // except if the current token is ELSE, CATCH or if we are in a switch/case
383         if (clearNonBlockIndents && (token != Scanner.TokenNameWHITESPACE)) {
384           switch (token) {
385             case TokenNameelse :
386               if (constructionsCount > 0 && constructions[constructionsCount - 1] == TokenNameelse) {
387                 pendingNewLines = 1;
388                 specialElse = true;
389               }
390               indentationLevel += popInclusiveUntil(TokenNameif);
391               break;
392               //                                                case TokenNamecatch :
393               //                                                        indentationLevel += popInclusiveUntil(TokenNamecatch);
394               //                                                        break;
395               //                                                case TokenNamefinally :
396               //                                                        indentationLevel += popInclusiveUntil(TokenNamecatch);
397               //                                                        break;
398             case TokenNamewhile :
399               if (nlicsToken == TokenNamedo) {
400                 indentationLevel += pop(TokenNamedo);
401                 break;
402               }
403             default :
404               indentationLevel += popExclusiveUntilBlockOrCase();
405               // clear until a CASE, DEFAULT or BLOCK is encountered.
406               // Thus, the indentationLevel is correctly cleared either
407               // in a switch/case statement or in any other situation.
408           }
409           clearNonBlockIndents = false;
410         }
411         // returns to the indentation level created by the SWITCH keyword
412         // if the current token is a CASE or a DEFAULT
413         if (token == TokenNamecase || token == TokenNamedefault) {
414           indentationLevel += pop(TokenNamecase);
415         }
416         //                              if (token == Scanner.TokenNamethrows) {
417         //                                      inThrowsClause = true;
418         //                              }
419         if ((token == Scanner.TokenNameclass // || token == Scanner.TokenNameinterface
420         )
421           && previousToken != Scanner.TokenNameDOT) {
422           inClassOrInterfaceHeader = true;
423         }
424
425         /* ## APPEND newlines and indentations to the output string
426         */
427         // Do not add a new line between ELSE and IF, if the option elseIfOnSameLine is true.
428         // Fix for 1ETLWPZ: IVJCOM:ALL - incorrect "else if" formatting
429         //        if (pendingNewlineAfterParen
430         //          && previousCompilableToken == TokenNameelse
431         //          && token == TokenNameif
432         //          && options.compactElseIfMode) {
433         //          pendingNewlineAfterParen = false;
434         //          pendingNewLines = 0;
435         //          indentationLevel += pop(TokenNameelse);
436         //          // because else if is now one single statement,
437         //          // the indentation level after it is increased by one and not by 2
438         //          // (else = 1 indent, if = 1 indent, but else if = 1 indent, not 2).
439         //        }
440         // Add a newline & indent to the formatted source string if
441         // a for/if-else/while statement was scanned and there is no block
442         // following it.
443         pendingNewlineAfterParen =
444           pendingNewlineAfterParen || (previousCompilableToken == TokenNameRPAREN && token == TokenNameLBRACE);
445         if (pendingNewlineAfterParen && token != Scanner.TokenNameWHITESPACE) {
446           pendingNewlineAfterParen = false;
447
448           // Do to add a newline & indent sequence if the current token is an
449           // open brace or a period or if the current token is a semi-colon and the
450           // previous token is a close paren.
451           // add a new line if a parenthesis belonging to a for() statement
452           // has been closed and the current token is not an opening brace
453           if (token != TokenNameLBRACE
454             && !isComment(token) // to avoid adding new line between else and a comment
455             && token != TokenNameDOT
456             && !(previousCompilableToken == TokenNameRPAREN && token == TokenNameSEMICOLON)) {
457             newLine(1);
458             currentLineIndentationLevel = indentationLevel;
459             pendingNewLines = 0;
460             pendingSpace = false;
461           } else {
462             if (token == TokenNameLBRACE && options.newLineBeforeOpeningBraceMode) {
463               newLine(1);
464               if (constructionsCount > 0
465                 && constructions[constructionsCount - 1] != BLOCK
466                 && constructions[constructionsCount - 1] != NONINDENT_BLOCK) {
467                 currentLineIndentationLevel = indentationLevel - 1;
468               } else {
469                 currentLineIndentationLevel = indentationLevel;
470               }
471               pendingNewLines = 0;
472               pendingSpace = false;
473             }
474           }
475         }
476         if (token == TokenNameLBRACE
477           && options.newLineBeforeOpeningBraceMode
478           && constructionsCount > 0
479           && constructions[constructionsCount - 1] == TokenNamedo) {
480           newLine(1);
481           currentLineIndentationLevel = indentationLevel - 1;
482           pendingNewLines = 0;
483           pendingSpace = false;
484         }
485         // see PR 1G5G8EC
486         if (token == TokenNameLBRACE && inThrowsClause) {
487           inThrowsClause = false;
488           if (options.newLineBeforeOpeningBraceMode) {
489             newLine(1);
490             currentLineIndentationLevel = indentationLevel;
491             pendingNewLines = 0;
492             pendingSpace = false;
493           }
494         }
495         // see PR 1G5G82G
496         if (token == TokenNameLBRACE && inClassOrInterfaceHeader) {
497           inClassOrInterfaceHeader = false;
498           if (options.newLineBeforeOpeningBraceMode) {
499             newLine(1);
500             currentLineIndentationLevel = indentationLevel;
501             pendingNewLines = 0;
502             pendingSpace = false;
503           }
504         }
505         // Add pending new lines to the formatted source string.
506         // Note: pending new lines are not added if the current token
507         // is a single line comment or whitespace.
508         // if the comment is between parenthesis, there is no blank line preservation
509         // (if it's a one-line comment, a blank line is added after it).
510         if (((pendingNewLines > 0 && (!isComment(token)))
511           || (newLinesInWhitespace > 0 && (openParenthesisCount <= 1 && isComment(token)))
512           || (previousCompilableToken == TokenNameLBRACE && token == TokenNameRBRACE))
513           && token != Scanner.TokenNameWHITESPACE) {
514
515           // Do not add newline & indent between an adjoining close brace and
516           // close paren.  Anonymous inner classes may use this form.
517           boolean closeBraceAndCloseParen = previousToken == TokenNameRBRACE && token == TokenNameRPAREN;
518
519           // OPTION (NewLineInCompoundStatement): do not add newline & indent
520           // between close brace and else, (do) while, catch, and finally if
521           // newlineInCompoundStatement is true.
522           boolean nlicsOption =
523             previousToken == TokenNameRBRACE
524               && !options.newlineInControlStatementMode
525               && (token == TokenNameelse || (token == TokenNamewhile && nlicsToken == TokenNamedo));
526           //                                                            || token == TokenNamecatch
527           //                                                            || token == TokenNamefinally);
528
529           // Do not add a newline & indent between a close brace and semi-colon.
530           boolean semiColonAndCloseBrace = previousToken == TokenNameRBRACE && token == TokenNameSEMICOLON;
531
532           // Do not add a new line & indent between a multiline comment and a opening brace
533           boolean commentAndOpenBrace = previousToken == Scanner.TokenNameCOMMENT_BLOCK && token == TokenNameLBRACE;
534
535           // Do not add a newline & indent between a close brace and a colon (in array assignments, for example).
536           boolean commaAndCloseBrace = previousToken == TokenNameRBRACE && token == TokenNameCOMMA;
537
538           // Add a newline and indent, if appropriate.
539           if (specialElse
540             || (!commentAndOpenBrace && !closeBraceAndCloseParen && !nlicsOption && !semiColonAndCloseBrace && !commaAndCloseBrace)) {
541
542             // if clearAllBlankLinesMode=false, leaves the blank lines
543             // inserted by the user
544             // if clearAllBlankLinesMode=true, removes all of then
545             // and insert only blank lines required by the formatting.
546             if (!options.clearAllBlankLinesMode) {
547               //  (isComment(token))
548               pendingNewLines = (pendingNewLines < newLinesInWhitespace) ? newLinesInWhitespace : pendingNewLines;
549               pendingNewLines = (pendingNewLines > 2) ? 2 : pendingNewLines;
550             }
551             if (previousCompilableToken == TokenNameLBRACE && token == TokenNameRBRACE) {
552               containsOpenCloseBraces = true;
553               indentationLevelForOpenCloseBraces = currentLineIndentationLevel;
554               if (isComment(previousToken)) {
555                 newLine(pendingNewLines);
556               } else {
557                 /*  if (!(constructionsCount > 1
558                         && constructions[constructionsCount-1] == NONINDENT_BLOCK
559                         && (constructions[constructionsCount-2] == TokenNamefor 
560                          || constructions[constructionsCount-2] == TokenNamewhile))) {*/
561                 if (options.newLineInEmptyBlockMode) {
562                   if (inArrayAssignment) {
563                     newLine(1); // array assigment with an empty block
564                   } else {
565                     newLine(pendingNewLines);
566                   }
567                 }
568                 // }
569               }
570             } else {
571               // see PR 1FKKC3U: LFCOM:WINNT - Format problem with a comment before the ';'
572               if (!((previousToken == Scanner.TokenNameCOMMENT_BLOCK || previousToken == Scanner.TokenNameCOMMENT_PHPDOC)
573                 && token == TokenNameSEMICOLON)) {
574                 newLine(pendingNewLines);
575               }
576             }
577             if (((previousCompilableToken == TokenNameSEMICOLON)
578               || (previousCompilableToken == TokenNameLBRACE)
579               || (previousCompilableToken == TokenNameRBRACE)
580               || (isComment(previousToken)))
581               && (token == TokenNameRBRACE)) {
582               indentationOffset = -1;
583               indentationLevel += popExclusiveUntilBlock();
584             }
585             if (previousToken == Scanner.TokenNameCOMMENT_LINE && inAssignment) {
586               // PR 1FI5IPO
587               currentLineIndentationLevel++;
588             } else {
589               currentLineIndentationLevel = indentationLevel + indentationOffset;
590             }
591             pendingSpace = false;
592             indentationOffset = 0;
593           }
594           pendingNewLines = 0;
595           newLinesInWhitespace = 0;
596           specialElse = false;
597
598           if (nlicsToken == TokenNamedo && token == TokenNamewhile) {
599             nlicsToken = 0;
600           }
601         }
602         switch (token) {
603           case TokenNameelse :
604             //                          case TokenNamefinally :
605             expectingOpenBrace = true;
606             pendingNewlineAfterParen = true;
607             indentationLevel += pushControlStatement(token);
608             break;
609           case TokenNamecase :
610           case TokenNamedefault :
611             if (tokenBeforeColonCount == tokenBeforeColon.length) {
612               System.arraycopy(
613                 tokenBeforeColon,
614                 0,
615                 (tokenBeforeColon = new int[tokenBeforeColonCount * 2]),
616                 0,
617                 tokenBeforeColonCount);
618             }
619             tokenBeforeColon[tokenBeforeColonCount++] = TokenNamecase;
620             indentationLevel += pushControlStatement(TokenNamecase);
621             break;
622           case TokenNameQUESTION :
623             if (tokenBeforeColonCount == tokenBeforeColon.length) {
624               System.arraycopy(
625                 tokenBeforeColon,
626                 0,
627                 (tokenBeforeColon = new int[tokenBeforeColonCount * 2]),
628                 0,
629                 tokenBeforeColonCount);
630             }
631             tokenBeforeColon[tokenBeforeColonCount++] = token;
632             break;
633           case TokenNameswitch :
634           case TokenNamefor :
635           case TokenNameif :
636           case TokenNamewhile :
637             if (openParenthesisCount == openParenthesis.length) {
638               System.arraycopy(openParenthesis, 0, (openParenthesis = new int[openParenthesisCount * 2]), 0, openParenthesisCount);
639             }
640             openParenthesis[openParenthesisCount++] = 0;
641             expectingOpenBrace = true;
642
643             indentationLevel += pushControlStatement(token);
644             break;
645             //                                  case TokenNametry :
646             //                                          pendingNewlineAfterParen = true;
647             //                                  case TokenNamecatch :
648             //                                          // several CATCH statements can be contiguous.
649             //                                          // a CATCH is encountered pop until first CATCH (if a CATCH follows a TRY it works the same way,
650             //                                          // as CATCH and TRY are the same token in the stack).
651             //                                          expectingOpenBrace = true;
652             //                                          indentationLevel += pushControlStatement(TokenNamecatch);
653             //                                          break;
654
655           case TokenNamedo :
656             expectingOpenBrace = true;
657             indentationLevel += pushControlStatement(token);
658             nlicsToken = token;
659             break;
660           case TokenNamenew :
661             break;
662           case TokenNameLPAREN :
663             //                                          if (previousToken == TokenNamesynchronized) {
664             //                                                  indentationLevel += pushControlStatement(previousToken);
665             //                                          } else {
666             // Put a space between the previous and current token if the
667             // previous token was not a keyword, open paren, logical
668             // compliment (eg: !), semi-colon, open brace, close brace,
669             // super, or this.
670             if (previousCompilableToken != TokenNameLBRACKET
671               && previousToken != TokenNameIdentifier
672               && previousToken != 0
673               && previousToken != TokenNameNOT
674               && previousToken != TokenNameLPAREN
675               && previousToken != TokenNameTWIDDLE
676               && previousToken != TokenNameSEMICOLON
677               && previousToken != TokenNameLBRACE
678               && previousToken != TokenNameRBRACE) {
679               //                                                                && previousToken != TokenNamesuper
680               //                                                                && previousToken != TokenNamethis) {
681               space();
682             }
683             // If in a for/if/while statement, increase the parenthesis count
684             // for the current openParenthesisCount
685             // else increase the count for stand alone parenthesis.
686             if (openParenthesisCount > 0)
687               openParenthesis[openParenthesisCount - 1]++;
688             else
689               openParenthesis[0]++;
690
691             pendingSpace = false;
692             //S                 }
693             break;
694           case TokenNameRPAREN :
695
696             // Decrease the parenthesis count
697             // if there is no more unclosed parenthesis,
698             // a new line and indent may be append (depending on the next token).
699             if ((openParenthesisCount > 1) && (openParenthesis[openParenthesisCount - 1] > 0)) {
700               openParenthesis[openParenthesisCount - 1]--;
701               if (openParenthesis[openParenthesisCount - 1] <= 0) {
702                 pendingNewlineAfterParen = true;
703                 inAssignment = false;
704                 openParenthesisCount--;
705               }
706             } else {
707               openParenthesis[0]--;
708             }
709             pendingSpace = false;
710             break;
711           case TokenNameLBRACE :
712             if ((previousCompilableToken == TokenNameRBRACKET) || (previousCompilableToken == TokenNameEQUAL)) {
713               //                  if (previousCompilableToken == TokenNameRBRACKET) {
714               inArrayAssignment = true;
715               inAssignment = false;
716             }
717             if (inArrayAssignment) {
718               indentationLevel += pushBlock();
719             } else {
720               // Add new line and increase indentation level after open brace.
721               pendingNewLines = 1;
722               indentationLevel += pushBlock();
723             }
724             break;
725           case TokenNameRBRACE :
726             if (previousCompilableToken == TokenNameRPAREN) {
727               pendingSpace = false;
728             }
729             if (inArrayAssignment) {
730               inArrayAssignment = false;
731               pendingNewLines = 1;
732               indentationLevel += popInclusiveUntilBlock();
733             } else {
734               pendingNewLines = 1;
735               indentationLevel += popInclusiveUntilBlock();
736
737               if (previousCompilableToken == TokenNameRPAREN) {
738                 // fix for 1FGDDV6: LFCOM:WIN98 - Weird splitting on message expression
739                 currentLineBuffer.append(options.lineSeparatorSequence);
740                 increaseLineDelta(options.lineSeparatorSequence.length);
741               }
742               if (constructionsCount > 0) {
743                 switch (constructions[constructionsCount - 1]) {
744                   case TokenNamefor :
745                     //indentationLevel += popExclusiveUntilBlock();
746                     //break;
747                   case TokenNameswitch :
748                   case TokenNameif :
749                   case TokenNameelse :
750                     //                                                                  case TokenNametry :
751                     //                                                                  case TokenNamecatch :
752                     //                                                                  case TokenNamefinally :
753                   case TokenNamewhile :
754                   case TokenNamedo :
755                     //                                                                  case TokenNamesynchronized :
756                     clearNonBlockIndents = true;
757                   default :
758                     break;
759                 }
760               }
761             }
762             break;
763           case TokenNameLBRACKET :
764             openBracketCount++;
765             pendingSpace = false;
766             break;
767           case TokenNameRBRACKET :
768             openBracketCount -= (openBracketCount > 0) ? 1 : 0;
769             // if there is no left bracket to close, the right bracket is ignored.
770             pendingSpace = false;
771             break;
772           case TokenNameCOMMA :
773           case TokenNameDOT :
774             pendingSpace = false;
775             break;
776           case TokenNameSEMICOLON :
777
778             // Do not generate line terminators in the definition of
779             // the for statement.
780             // if not in this case, jump a line and reduce indentation after the brace
781             // if the block it closes belongs to a conditional statement (if, while, do...).
782             if (openParenthesisCount <= 1) {
783               pendingNewLines = 1;
784               if (expectingOpenBrace) {
785                 clearNonBlockIndents = true;
786                 expectingOpenBrace = false;
787               }
788             }
789             inAssignment = false;
790             pendingSpace = false;
791             break;
792           case TokenNamePLUS_PLUS :
793           case TokenNameMINUS_MINUS :
794
795             // Do not put a space between a post-increment/decrement
796             // and the identifier being modified.
797             if (previousToken == TokenNameIdentifier || previousToken == TokenNameRBRACKET) {
798               pendingSpace = false;
799             }
800             break;
801           case TokenNamePLUS : // previously ADDITION
802           case TokenNameMINUS :
803
804             // Handle the unary operators plus and minus via a flag
805             if (!isLiteralToken(previousToken)
806               && previousToken != TokenNameIdentifier
807               && previousToken != TokenNameRPAREN
808               && previousToken != TokenNameRBRACKET) {
809               unarySignModifier = 1;
810             }
811             break;
812           case TokenNameCOLON :
813             // In a switch/case statement, add a newline & indent
814             // when a colon is encountered.
815             if (tokenBeforeColonCount > 0) {
816               if (tokenBeforeColon[tokenBeforeColonCount - 1] == TokenNamecase) {
817                 pendingNewLines = 1;
818               }
819               tokenBeforeColonCount--;
820             }
821             break;
822           case TokenNameEQUAL :
823             inAssignment = true;
824             break;
825           case Scanner.TokenNameCOMMENT_LINE :
826             pendingNewLines = 1;
827             if (inAssignment) {
828               currentLineIndentationLevel++;
829             }
830             break; // a line is always inserted after a one-line comment
831           case Scanner.TokenNameCOMMENT_PHPDOC :
832           case Scanner.TokenNameCOMMENT_BLOCK :
833             currentCommentOffset = getCurrentCommentOffset();
834             pendingNewLines = 1;
835             break;
836           case Scanner.TokenNameWHITESPACE :
837
838             // Count the number of line terminators in the whitespace so
839             // line spacing can be preserved near comments.
840             char[] source = scanner.source;
841             newLinesInWhitespace = 0;
842             for (int i = scanner.startPosition, max = scanner.currentPosition; i < max; i++) {
843               if (source[i] == '\r') {
844                 if (i < max - 1) {
845                   if (source[++i] == '\n') {
846                     newLinesInWhitespace++;
847                   } else {
848                     newLinesInWhitespace++;
849                   }
850                 } else {
851                   newLinesInWhitespace++;
852                 }
853               } else if (source[i] == '\n') {
854                 newLinesInWhitespace++;
855               }
856             }
857             increaseLineDelta(scanner.startPosition - scanner.currentPosition);
858             break;
859 //          case TokenNameHTML :
860 //            // Add the next token to the formatted source string.
861 //            // outputCurrentToken(token);
862 //            int startPosition = scanner.startPosition;
863 //            flushBuffer();
864 //            for (int i = startPosition, max = scanner.currentPosition; i < max; i++) {
865 //              char currentCharacter = scanner.source[i];
866 //              updateMappedPositions(i);
867 //              currentLineBuffer.append(currentCharacter);
868 //            }
869 //            break;
870           default :
871             if ((token == TokenNameIdentifier) || isLiteralToken(token)) {
872               //                                                        || token == TokenNamesuper
873               //                                                        || token == TokenNamethis) {
874
875               // Do not put a space between a unary operator
876               // (eg: ++, --, +, -) and the identifier being modified.
877               if (previousToken == TokenNamePLUS_PLUS
878                 || previousToken == TokenNameMINUS_MINUS
879                 || (previousToken == TokenNamePLUS && unarySignModifier > 0)
880                 || (previousToken == TokenNameMINUS && unarySignModifier > 0)) {
881                 pendingSpace = false;
882               }
883               unarySignModifier = 0;
884             }
885             break;
886         }
887         // Do not output whitespace tokens.
888         if (token != Scanner.TokenNameWHITESPACE) {
889
890           /* Add pending space to the formatted source string.
891           Do not output a space under the following circumstances:
892           1) this is the first pass
893           2) previous token is an open paren
894           3) previous token is a period
895           4) previous token is the logical compliment (eg: !)
896           5) previous token is the bitwise compliment (eg: ~)
897           6) previous token is the open bracket (eg: [)
898           7) in an assignment statement, if the previous token is an 
899           open brace or the current token is a close brace
900           8) previous token is a single line comment
901           */
902           boolean openAndCloseBrace = previousCompilableToken == TokenNameLBRACE && token == TokenNameRBRACE;
903
904           if (pendingSpace
905             && insertSpaceAfter(previousToken)
906             && !(inAssignment && (previousToken == TokenNameLBRACE || token == TokenNameRBRACE))
907             && previousToken != Scanner.TokenNameCOMMENT_LINE) {
908             if ((!(options.compactAssignmentMode && token == TokenNameEQUAL)) && !openAndCloseBrace)
909               space();
910           }
911           // Add the next token to the formatted source string.
912           outputCurrentToken(token);
913           if (token == Scanner.TokenNameCOMMENT_LINE && openParenthesisCount > 1) {
914             pendingNewLines = 0;
915             currentLineBuffer.append(options.lineSeparatorSequence);
916             increaseLineDelta(options.lineSeparatorSequence.length);
917           }
918           pendingSpace = true;
919         }
920         // Whitespace tokens do not need to be remembered.
921         if (token != Scanner.TokenNameWHITESPACE) {
922           previousToken = token;
923           if (token != Scanner.TokenNameCOMMENT_BLOCK
924             && token != Scanner.TokenNameCOMMENT_LINE
925             && token != Scanner.TokenNameCOMMENT_PHPDOC) {
926             previousCompilableToken = token;
927           }
928         }
929       }
930       output(copyRemainingSource());
931       flushBuffer();
932       // dump the last token of the source in the formatted output.
933     } catch (InvalidInputException e) {
934       output(copyRemainingSource());
935       flushBuffer();
936       // dump the last token of the source in the formatted output.
937     }
938   }
939
940   /** 
941    * Formats the char array <code>sourceString</code>,
942    * and returns a string containing the formatted version.
943    * @return the formatted ouput.
944    */
945   public String formatSourceString(String sourceString) {
946     char[] sourceChars = sourceString.toCharArray();
947     formattedSource = new StringBuffer(sourceChars.length);
948     scanner.setSource(sourceChars);
949     format();
950     return formattedSource.toString();
951   }
952
953   /** 
954    * Formats the char array <code>sourceString</code>,
955    * and returns a string containing the formatted version.
956    * @param string the string to format
957    * @param indentationLevel the initial indentation level
958    * @return the formatted ouput.
959    */
960   public String format(String string, int indentationLevel) {
961     return format(string, indentationLevel, (int[]) null);
962   }
963
964   /** 
965    * Formats the char array <code>sourceString</code>,
966    * and returns a string containing the formatted version.
967    * The positions array is modified to contain the mapped positions.
968    * @param string the string to format
969    * @param indentationLevel the initial indentation level
970    * @param positions the array of positions to map
971    * @return the formatted ouput.
972    */
973   public String format(String string, int indentationLevel, int[] positions) {
974     return this.format(string, indentationLevel, positions, null);
975   }
976
977   public String format(String string, int indentationLevel, int[] positions, String lineSeparator) {
978     if (lineSeparator != null) {
979       this.options.setLineSeparator(lineSeparator);
980     }
981     if (positions != null) {
982       this.setPositionsToMap(positions);
983       this.setInitialIndentationLevel(indentationLevel);
984       String formattedString = this.formatSourceString(string);
985       int[] mappedPositions = this.getMappedPositions();
986       System.arraycopy(mappedPositions, 0, positions, 0, positions.length);
987       return formattedString;
988     } else {
989       this.setInitialIndentationLevel(indentationLevel);
990       return this.formatSourceString(string);
991     }
992   }
993   /** 
994    * Formats the char array <code>sourceString</code>,
995    * and returns a string containing the formatted version. The initial indentation level is 0.
996    * @param string the string to format
997    * @return the formatted ouput.
998    */
999   public String format(String string) {
1000     return this.format(string, 0, (int[]) null);
1001   }
1002
1003   /** 
1004    * Formats a given source string, starting indenting it at a particular 
1005    * depth and using the given options
1006    * 
1007    * @deprecated backport 1.0 internal functionality
1008    */
1009   public static String format(String sourceString, int initialIndentationLevel, ConfigurableOption[] options) {
1010     CodeFormatter formatter = new CodeFormatter(options);
1011     formatter.setInitialIndentationLevel(initialIndentationLevel);
1012     return formatter.formatSourceString(sourceString);
1013   }
1014
1015   /**
1016    * Returns the number of characters and tab char between the beginning of the line
1017    * and the beginning of the comment.
1018    */
1019   private int getCurrentCommentOffset() {
1020     int linePtr = scanner.linePtr;
1021     // if there is no beginning of line, return 0.
1022     if (linePtr < 0)
1023       return 0;
1024     int offset = 0;
1025     int beginningOfLine = scanner.lineEnds[linePtr];
1026     int currentStartPosition = scanner.startPosition;
1027     char[] source = scanner.source;
1028
1029     // find the position of the beginning of the line containing the comment
1030     while (beginningOfLine > currentStartPosition) {
1031       if (linePtr > 0) {
1032         beginningOfLine = scanner.lineEnds[--linePtr];
1033       } else {
1034         beginningOfLine = 0;
1035         break;
1036       }
1037     }
1038     for (int i = currentStartPosition - 1; i >= beginningOfLine; i--) {
1039       char currentCharacter = source[i];
1040       switch (currentCharacter) {
1041         case '\t' :
1042           offset += options.tabSize;
1043           break;
1044         case ' ' :
1045           offset++;
1046           break;
1047         case '\r' :
1048         case '\n' :
1049           break;
1050         default :
1051           return offset;
1052       }
1053     }
1054     return offset;
1055   }
1056
1057   /**
1058    * Returns an array of descriptions for the configurable options.
1059    * The descriptions may be changed and passed back to a different
1060    * compiler.
1061    * 
1062    * @deprecated backport 1.0 internal functionality
1063    */
1064   public static ConfigurableOption[] getDefaultOptions(Locale locale) {
1065     String componentName = CodeFormatter.class.getName();
1066     FormatterOptions options = new FormatterOptions();
1067     return new ConfigurableOption[] { new ConfigurableOption(componentName, "newline.openingBrace", locale, options.newLineBeforeOpeningBraceMode ? 0 : 1), //$NON-NLS-1$
1068       new ConfigurableOption(componentName, "newline.controlStatement", locale, options.newlineInControlStatementMode ? 0 : 1), //$NON-NLS-1$
1069       new ConfigurableOption(componentName, "newline.clearAll", locale, options.clearAllBlankLinesMode ? 0 : 1), //$NON-NLS-1$
1070       //      new ConfigurableOption(componentName, "newline.elseIf", locale, options.compactElseIfMode ? 0 : 1), //$NON-NLS-1$
1071       new ConfigurableOption(componentName, "newline.emptyBlock", locale, options.newLineInEmptyBlockMode ? 0 : 1), //$NON-NLS-1$
1072       new ConfigurableOption(componentName, "line.split", locale, options.maxLineLength), //$NON-NLS-1$
1073       new ConfigurableOption(componentName, "style.compactAssignment", locale, options.compactAssignmentMode ? 0 : 1), //$NON-NLS-1$
1074       new ConfigurableOption(componentName, "tabulation.char", locale, options.indentWithTab ? 0 : 1), //$NON-NLS-1$
1075       new ConfigurableOption(componentName, "tabulation.size", locale, options.tabSize) //$NON-NLS-1$
1076     };
1077   }
1078
1079   /**
1080    * Returns the array of mapped positions.
1081    * Returns null is no positions have been set.
1082    * @return int[]
1083    * @deprecated There is no need to retrieve the mapped positions anymore.
1084    */
1085   public int[] getMappedPositions() {
1086     return mappedPositions;
1087   }
1088
1089   /**
1090    * Returns the priority of the token given as argument<br>
1091    * The most prioritary the token is, the smallest the return value is.
1092    * @return the priority of <code>token</code>
1093    * @param token the token of which the priority is requested
1094    */
1095   private static int getTokenPriority(int token) {
1096     switch (token) {
1097       case TokenNameextends :
1098         //                      case TokenNameimplements :
1099         //                      case TokenNamethrows :
1100         return 10;
1101       case TokenNameSEMICOLON : // ;
1102         return 20;
1103       case TokenNameCOMMA : // ,
1104         return 25;
1105       case TokenNameEQUAL : // =
1106         return 30;
1107       case TokenNameAND_AND : // && 
1108       case TokenNameOR_OR : // || 
1109         return 40;
1110       case TokenNameQUESTION : // ? 
1111       case TokenNameCOLON : // :
1112         return 50; // it's better cutting on ?: than on ;
1113       case TokenNameEQUAL_EQUAL : // == 
1114       case TokenNameNOT_EQUAL : // != 
1115         return 60;
1116       case TokenNameLESS : // < 
1117       case TokenNameLESS_EQUAL : // <= 
1118       case TokenNameGREATER : // > 
1119       case TokenNameGREATER_EQUAL : // >= 
1120         //                      case TokenNameinstanceof : // instanceof
1121         return 70;
1122       case TokenNamePLUS : // + 
1123       case TokenNameMINUS : // - 
1124         return 80;
1125       case TokenNameMULTIPLY : // * 
1126       case TokenNameDIVIDE : // / 
1127       case TokenNameREMAINDER : // % 
1128         return 90;
1129       case TokenNameLEFT_SHIFT : // << 
1130       case TokenNameRIGHT_SHIFT : // >> 
1131         //                      case TokenNameUNSIGNED_RIGHT_SHIFT : // >>> 
1132         return 100;
1133       case TokenNameAND : // &
1134       case TokenNameOR : // | 
1135       case TokenNameXOR : // ^ 
1136         return 110;
1137       case TokenNameMULTIPLY_EQUAL : // *= 
1138       case TokenNameDIVIDE_EQUAL : // /= 
1139       case TokenNameREMAINDER_EQUAL : // %= 
1140       case TokenNamePLUS_EQUAL : // += 
1141       case TokenNameMINUS_EQUAL : // -= 
1142       case TokenNameLEFT_SHIFT_EQUAL : // <<= 
1143       case TokenNameRIGHT_SHIFT_EQUAL : // >>= 
1144         //                      case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>=
1145       case TokenNameAND_EQUAL : // &= 
1146       case TokenNameXOR_EQUAL : // ^= 
1147       case TokenNameOR_EQUAL : // |= 
1148         return 120;
1149       case TokenNameDOT : // .
1150         return 130;
1151       default :
1152         return Integer.MAX_VALUE;
1153     }
1154   }
1155
1156   /**
1157    * Handles the exception raised when an invalid token is encountered.
1158    * Returns true if the exception has been handled, false otherwise.
1159    */
1160   private boolean handleInvalidToken(Exception e) {
1161     if (e.getMessage().equals(Scanner.INVALID_CHARACTER_CONSTANT)
1162       || e.getMessage().equals(Scanner.INVALID_CHAR_IN_STRING)
1163       || e.getMessage().equals(Scanner.INVALID_ESCAPE)) {
1164       return true;
1165     }
1166     return false;
1167   }
1168
1169   private final void increaseGlobalDelta(int offset) {
1170     globalDelta += offset;
1171   }
1172
1173   private final void increaseLineDelta(int offset) {
1174     lineDelta += offset;
1175   }
1176
1177   private final void increaseSplitDelta(int offset) {
1178     splitDelta += offset;
1179   }
1180
1181   /**
1182    * Returns true if a space has to be inserted after <code>operator</code>
1183    * false otherwise.
1184    */
1185   private boolean insertSpaceAfter(int token) {
1186     switch (token) {
1187       case TokenNameLPAREN :
1188       case TokenNameNOT :
1189       case TokenNameTWIDDLE :
1190       case TokenNameDOT :
1191       case 0 : // no token
1192       case TokenNameLBRACKET :
1193       case Scanner.TokenNameCOMMENT_LINE :
1194         return false;
1195       default :
1196         return true;
1197     }
1198   }
1199
1200   /**
1201    * Returns true if a space has to be inserted before <code>operator</code>
1202    * false otherwise.<br>
1203    * Cannot be static as it uses the code formatter options
1204    * (to know if the compact assignment mode is on).
1205    */
1206   private boolean insertSpaceBefore(int token) {
1207     switch (token) {
1208       case TokenNameEQUAL :
1209         return (!options.compactAssignmentMode);
1210       default :
1211         return false;
1212     }
1213   }
1214
1215   private static boolean isComment(int token) {
1216     boolean result =
1217       token == Scanner.TokenNameCOMMENT_BLOCK || token == Scanner.TokenNameCOMMENT_LINE || token == Scanner.TokenNameCOMMENT_PHPDOC;
1218     return result;
1219   }
1220
1221   private static boolean isLiteralToken(int token) {
1222     boolean result = token == TokenNameIntegerLiteral
1223       //                        || token == TokenNameLongLiteral
1224     //                  || token == TokenNameFloatingPointLiteral
1225   || token == TokenNameDoubleLiteral
1226       //                        || token == TokenNameCharacterLiteral
1227   || token == TokenNameStringLiteral;
1228     return result;
1229   }
1230
1231   /**
1232    * If the length of <code>oneLineBuffer</code> exceeds <code>maxLineLength</code>,
1233    * it is split and the result is dumped in <code>formattedSource</code>
1234    * @param newLineCount the number of new lines to append
1235    */
1236   private void newLine(int newLineCount) {
1237
1238     // format current line
1239     splitDelta = 0;
1240     beginningOfLineIndex = formattedSource.length();
1241     String currentLine = currentLineBuffer.toString();
1242     if (containsOpenCloseBraces) {
1243       containsOpenCloseBraces = false;
1244       outputLine(currentLine, false, indentationLevelForOpenCloseBraces, 0, -1, null, 0);
1245       indentationLevelForOpenCloseBraces = currentLineIndentationLevel;
1246     } else {
1247       outputLine(currentLine, false, currentLineIndentationLevel, 0, -1, null, 0);
1248     }
1249     // dump line break(s)
1250     for (int i = 0; i < newLineCount; i++) {
1251       formattedSource.append(options.lineSeparatorSequence);
1252       increaseSplitDelta(options.lineSeparatorSequence.length);
1253     }
1254     // reset formatter for next line
1255     int currentLength = currentLine.length();
1256     currentLineBuffer = new StringBuffer(currentLength > maxLineSize ? maxLineSize = currentLength : maxLineSize);
1257
1258     increaseGlobalDelta(splitDelta);
1259     increaseGlobalDelta(lineDelta);
1260     lineDelta = 0;
1261     currentLineIndentationLevel = initialIndentationLevel;
1262   }
1263
1264   private String operatorString(int operator) {
1265     switch (operator) {
1266       case TokenNameextends :
1267         return "extends"; //$NON-NLS-1$
1268
1269         //                      case TokenNameimplements :
1270         //                              return "implements"; //$NON-NLS-1$
1271         //
1272         //                      case TokenNamethrows :
1273         //                              return "throws"; //$NON-NLS-1$
1274
1275       case TokenNameSEMICOLON : // ;
1276         return ";"; //$NON-NLS-1$
1277
1278       case TokenNameCOMMA : // ,
1279         return ","; //$NON-NLS-1$
1280
1281       case TokenNameEQUAL : // =
1282         return "="; //$NON-NLS-1$
1283
1284       case TokenNameAND_AND : // && (15.22)
1285         return "&&"; //$NON-NLS-1$
1286
1287       case TokenNameOR_OR : // || (15.23)
1288         return "||"; //$NON-NLS-1$
1289
1290       case TokenNameQUESTION : // ? (15.24)
1291         return "?"; //$NON-NLS-1$
1292
1293       case TokenNameCOLON : // : (15.24)
1294         return ":"; //$NON-NLS-1$
1295
1296       case TokenNameEQUAL_EQUAL : // == (15.20, 15.20.1, 15.20.2, 15.20.3)
1297         return "=="; //$NON-NLS-1$
1298
1299       case TokenNameNOT_EQUAL : // != (15.20, 15.20.1, 15.20.2, 15.20.3)
1300         return "!="; //$NON-NLS-1$
1301
1302       case TokenNameLESS : // < (15.19.1)
1303         return "<"; //$NON-NLS-1$
1304
1305       case TokenNameLESS_EQUAL : // <= (15.19.1)
1306         return "<="; //$NON-NLS-1$
1307
1308       case TokenNameGREATER : // > (15.19.1)
1309         return ">"; //$NON-NLS-1$
1310
1311       case TokenNameGREATER_EQUAL : // >= (15.19.1)
1312         return ">="; //$NON-NLS-1$
1313
1314         //                      case TokenNameinstanceof : // instanceof
1315         //                              return "instanceof"; //$NON-NLS-1$
1316
1317       case TokenNamePLUS : // + (15.17, 15.17.2)
1318         return "+"; //$NON-NLS-1$
1319
1320       case TokenNameMINUS : // - (15.17.2)
1321         return "-"; //$NON-NLS-1$
1322
1323       case TokenNameMULTIPLY : // * (15.16.1)
1324         return "*"; //$NON-NLS-1$
1325
1326       case TokenNameDIVIDE : // / (15.16.2)
1327         return "/"; //$NON-NLS-1$
1328
1329       case TokenNameREMAINDER : // % (15.16.3)
1330         return "%"; //$NON-NLS-1$
1331
1332       case TokenNameLEFT_SHIFT : // << (15.18)
1333         return "<<"; //$NON-NLS-1$
1334
1335       case TokenNameRIGHT_SHIFT : // >> (15.18)
1336         return ">>"; //$NON-NLS-1$
1337
1338         //                      case TokenNameUNSIGNED_RIGHT_SHIFT : // >>> (15.18)
1339         //                              return ">>>"; //$NON-NLS-1$
1340
1341       case TokenNameAND : // & (15.21, 15.21.1, 15.21.2)
1342         return "&"; //$NON-NLS-1$
1343
1344       case TokenNameOR : // | (15.21, 15.21.1, 15.21.2)
1345         return "|"; //$NON-NLS-1$
1346
1347       case TokenNameXOR : // ^ (15.21, 15.21.1, 15.21.2)
1348         return "^"; //$NON-NLS-1$
1349
1350       case TokenNameMULTIPLY_EQUAL : // *= (15.25.2)
1351         return "*="; //$NON-NLS-1$
1352
1353       case TokenNameDIVIDE_EQUAL : // /= (15.25.2)
1354         return "/="; //$NON-NLS-1$
1355
1356       case TokenNameREMAINDER_EQUAL : // %= (15.25.2)
1357         return "%="; //$NON-NLS-1$
1358
1359       case TokenNamePLUS_EQUAL : // += (15.25.2)
1360         return "+="; //$NON-NLS-1$
1361
1362       case TokenNameMINUS_EQUAL : // -= (15.25.2)
1363         return "-="; //$NON-NLS-1$
1364
1365       case TokenNameLEFT_SHIFT_EQUAL : // <<= (15.25.2)
1366         return "<<="; //$NON-NLS-1$
1367
1368       case TokenNameRIGHT_SHIFT_EQUAL : // >>= (15.25.2)
1369         return ">>="; //$NON-NLS-1$
1370
1371         //                      case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>= (15.25.2)
1372         //                              return ">>>="; //$NON-NLS-1$
1373
1374       case TokenNameAND_EQUAL : // &= (15.25.2)
1375         return "&="; //$NON-NLS-1$
1376
1377       case TokenNameXOR_EQUAL : // ^= (15.25.2)
1378         return "^="; //$NON-NLS-1$
1379
1380       case TokenNameOR_EQUAL : // |= (15.25.2)
1381         return "|="; //$NON-NLS-1$
1382
1383       case TokenNameDOT : // .
1384         return "."; //$NON-NLS-1$
1385
1386       default :
1387         return ""; //$NON-NLS-1$
1388     }
1389   }
1390
1391   /** 
1392    * Appends <code>stringToOutput</code> to the formatted output.<br>
1393    * If it contains \n, append a LINE_SEPARATOR and indent after it.
1394    */
1395   private void output(String stringToOutput) {
1396     char currentCharacter;
1397     for (int i = 0, max = stringToOutput.length(); i < max; i++) {
1398       currentCharacter = stringToOutput.charAt(i);
1399       if (currentCharacter != '\t') {
1400         currentLineBuffer.append(currentCharacter);
1401       }
1402     }
1403   }
1404
1405   /** 
1406    * Appends <code>token</code> to the formatted output.<br>
1407    * If it contains <code>\n</code>, append a LINE_SEPARATOR and indent after it.
1408    */
1409   private void outputCurrentToken(int token) {
1410     char[] source = scanner.source;
1411     int startPosition = scanner.startPosition;
1412
1413     switch (token) {
1414       case Scanner.TokenNameCOMMENT_PHPDOC :
1415       case Scanner.TokenNameCOMMENT_BLOCK :
1416       case Scanner.TokenNameCOMMENT_LINE :
1417         boolean endOfLine = false;
1418         int currentCommentOffset = getCurrentCommentOffset();
1419         int beginningOfLineSpaces = 0;
1420         endOfLine = false;
1421         currentCommentOffset = getCurrentCommentOffset();
1422         beginningOfLineSpaces = 0;
1423         boolean pendingCarriageReturn = false;
1424         for (int i = startPosition, max = scanner.currentPosition; i < max; i++) {
1425           char currentCharacter = source[i];
1426           updateMappedPositions(i);
1427           switch (currentCharacter) {
1428             case '\r' :
1429               pendingCarriageReturn = true;
1430               endOfLine = true;
1431               break;
1432             case '\n' :
1433               if (pendingCarriageReturn) {
1434                 increaseGlobalDelta(options.lineSeparatorSequence.length - 2);
1435               } else {
1436                 increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
1437               }
1438               pendingCarriageReturn = false;
1439               currentLineBuffer.append(options.lineSeparatorSequence);
1440               beginningOfLineSpaces = 0;
1441               endOfLine = true;
1442               break;
1443             case '\t' :
1444               if (pendingCarriageReturn) {
1445                 pendingCarriageReturn = false;
1446                 increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
1447                 currentLineBuffer.append(options.lineSeparatorSequence);
1448                 beginningOfLineSpaces = 0;
1449                 endOfLine = true;
1450               }
1451               if (endOfLine) {
1452                 // we remove a maximum of currentCommentOffset characters (tabs are converted to space numbers).
1453                 beginningOfLineSpaces += options.tabSize;
1454                 if (beginningOfLineSpaces > currentCommentOffset) {
1455                   currentLineBuffer.append(currentCharacter);
1456                 } else {
1457                   increaseGlobalDelta(-1);
1458                 }
1459               } else {
1460                 currentLineBuffer.append(currentCharacter);
1461               }
1462               break;
1463             case ' ' :
1464               if (pendingCarriageReturn) {
1465                 pendingCarriageReturn = false;
1466                 increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
1467                 currentLineBuffer.append(options.lineSeparatorSequence);
1468                 beginningOfLineSpaces = 0;
1469                 endOfLine = true;
1470               }
1471               if (endOfLine) {
1472                 // we remove a maximum of currentCommentOffset characters (tabs are converted to space numbers).
1473                 beginningOfLineSpaces++;
1474                 if (beginningOfLineSpaces > currentCommentOffset) {
1475                   currentLineBuffer.append(currentCharacter);
1476                 } else {
1477                   increaseGlobalDelta(-1);
1478                 }
1479               } else {
1480                 currentLineBuffer.append(currentCharacter);
1481               }
1482               break;
1483             default :
1484               if (pendingCarriageReturn) {
1485                 pendingCarriageReturn = false;
1486                 increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
1487                 currentLineBuffer.append(options.lineSeparatorSequence);
1488                 beginningOfLineSpaces = 0;
1489                 endOfLine = true;
1490               } else {
1491                 beginningOfLineSpaces = 0;
1492                 currentLineBuffer.append(currentCharacter);
1493                 endOfLine = false;
1494               }
1495           }
1496         }
1497         updateMappedPositions(scanner.currentPosition - 1);
1498         multipleLineCommentCounter++;
1499         break;
1500       default :
1501         for (int i = startPosition, max = scanner.currentPosition; i < max; i++) {
1502           char currentCharacter = source[i];
1503           updateMappedPositions(i);
1504           currentLineBuffer.append(currentCharacter);
1505         }
1506     }
1507   }
1508
1509   /**
1510    * Outputs <code>currentString</code>:<br>
1511    * <ul><li>If its length is < maxLineLength, output
1512    * <li>Otherwise it is split.</ul>
1513    * @param currentString string to output
1514    * @param preIndented whether the string to output was pre-indented
1515    * @param depth number of indentation to put in front of <code>currentString</code>
1516    * @param operator value of the operator belonging to <code>currentString</code>.
1517    */
1518   private void outputLine(
1519     String currentString,
1520     boolean preIndented,
1521     int depth,
1522     int operator,
1523     int substringIndex,
1524     int[] startSubstringIndexes,
1525     int offsetInGlobalLine) {
1526
1527     boolean emptyFirstSubString = false;
1528     String operatorString = operatorString(operator);
1529     boolean placeOperatorBehind = !breakLineBeforeOperator(operator);
1530     boolean placeOperatorAhead = !placeOperatorBehind;
1531
1532     // dump prefix operator?
1533     if (placeOperatorAhead) {
1534       if (!preIndented) {
1535         dumpTab(depth);
1536         preIndented = true;
1537       }
1538       if (operator != 0) {
1539         if (insertSpaceBefore(operator)) {
1540           formattedSource.append(' ');
1541           increaseSplitDelta(1);
1542         }
1543         formattedSource.append(operatorString);
1544         increaseSplitDelta(operatorString.length());
1545
1546         if (insertSpaceAfter(operator) //                       && operator != TokenNameimplements
1547         && operator != TokenNameextends) {
1548           //                    && operator != TokenNamethrows) {
1549           formattedSource.append(' ');
1550           increaseSplitDelta(1);
1551         }
1552       }
1553     }
1554     SplitLine splitLine = null;
1555     if (options.maxLineLength == 0
1556       || getLength(currentString, depth) < options.maxLineLength
1557       || (splitLine = split(currentString, offsetInGlobalLine)) == null) {
1558
1559       // depending on the type of operator, outputs new line before of after dumping it
1560       // indent before postfix operator
1561       // indent also when the line cannot be split
1562       if (operator == TokenNameextends) {
1563         //                              || operator == TokenNameimplements
1564         //                              || operator == TokenNamethrows) {
1565         formattedSource.append(' ');
1566         increaseSplitDelta(1);
1567       }
1568       if (placeOperatorBehind) {
1569         if (!preIndented) {
1570           dumpTab(depth);
1571         }
1572       }
1573       int max = currentString.length();
1574       if (multipleLineCommentCounter != 0) {
1575         try {
1576           BufferedReader reader = new BufferedReader(new StringReader(currentString));
1577           String line = reader.readLine();
1578           while (line != null) {
1579             updateMappedPositionsWhileSplitting(
1580               beginningOfLineIndex,
1581               beginningOfLineIndex + line.length() + options.lineSeparatorSequence.length);
1582             formattedSource.append(line);
1583             beginningOfLineIndex = beginningOfLineIndex + line.length();
1584             if ((line = reader.readLine()) != null) {
1585               formattedSource.append(options.lineSeparatorSequence);
1586               beginningOfLineIndex += options.lineSeparatorSequence.length;
1587               dumpTab(currentLineIndentationLevel);
1588             }
1589           }
1590           reader.close();
1591         } catch (IOException e) {
1592           e.printStackTrace();
1593         }
1594       } else {
1595         updateMappedPositionsWhileSplitting(beginningOfLineIndex, beginningOfLineIndex + max);
1596         for (int i = 0; i < max; i++) {
1597           char currentChar = currentString.charAt(i);
1598           switch (currentChar) {
1599             case '\r' :
1600               break;
1601             case '\n' :
1602               if (i != max - 1) {
1603                 // fix for 1FFYL5C: LFCOM:ALL - Incorrect indentation when split with a comment inside a condition
1604                 // a substring cannot end with a lineSeparatorSequence,
1605                 // except if it has been added by format() after a one-line comment
1606                 formattedSource.append(options.lineSeparatorSequence);
1607
1608                 // 1FGDDV6: LFCOM:WIN98 - Weird splitting on message expression
1609                 dumpTab(depth - 1);
1610               }
1611               break;
1612             default :
1613               formattedSource.append(currentChar);
1614           }
1615         }
1616       }
1617       // update positions inside the mappedPositions table
1618       if (substringIndex != -1) {
1619         if (multipleLineCommentCounter == 0) {
1620           int startPosition = beginningOfLineIndex + startSubstringIndexes[substringIndex];
1621           updateMappedPositionsWhileSplitting(startPosition, startPosition + max);
1622         }
1623
1624         // compute the splitDelta resulting with the operator and blank removal
1625         if (substringIndex + 1 != startSubstringIndexes.length) {
1626           increaseSplitDelta(startSubstringIndexes[substringIndex] + max - startSubstringIndexes[substringIndex + 1]);
1627         }
1628       }
1629       // dump postfix operator?
1630       if (placeOperatorBehind) {
1631         if (insertSpaceBefore(operator)) {
1632           formattedSource.append(' ');
1633           if (operator != 0) {
1634             increaseSplitDelta(1);
1635           }
1636         }
1637         formattedSource.append(operatorString);
1638         if (operator != 0) {
1639           increaseSplitDelta(operatorString.length());
1640         }
1641       }
1642       return;
1643     }
1644     // fix for 1FG0BA3: LFCOM:WIN98 - Weird splitting on interfaces
1645     // extends has to stand alone on a line when currentString has been split.
1646     if (options.maxLineLength != 0 && splitLine != null && (operator == TokenNameextends)) {
1647       //                                || operator == TokenNameimplements
1648       //                                || operator == TokenNamethrows)) {
1649       formattedSource.append(options.lineSeparatorSequence);
1650       increaseSplitDelta(options.lineSeparatorSequence.length);
1651       dumpTab(depth + 1);
1652     } else {
1653       if (operator == TokenNameextends) {
1654         //                              || operator == TokenNameimplements
1655         //                              || operator == TokenNamethrows) {
1656         formattedSource.append(' ');
1657         increaseSplitDelta(1);
1658       }
1659     }
1660     // perform actual splitting
1661     String result[] = splitLine.substrings;
1662     int[] splitOperators = splitLine.operators;
1663
1664     if (result[0].length() == 0) {
1665       // when the substring 0 is null, the substring 1 is correctly indented.
1666       depth--;
1667       emptyFirstSubString = true;
1668     }
1669     // the operator going in front of the result[0] string is the operator parameter
1670     for (int i = 0, max = result.length; i < max; i++) {
1671       // the new depth is the current one if this is the first substring,
1672       // the current one + 1 otherwise.
1673       // if the substring is a comment, use the current indentation Level instead of the depth
1674       // (-1 because the ouputline increases depth).
1675       // (fix for 1FFC72R: LFCOM:ALL - Incorrect line split in presence of line comments)
1676       String currentResult = result[i];
1677
1678       if (currentResult.length() != 0 || splitOperators[i] != 0) {
1679           int newDepth = (currentResult.startsWith("/*") //$NON-NLS-1$
1680     || currentResult.startsWith("//")) //$NON-NLS-1$ 
1681   ? indentationLevel - 1 : depth;
1682         outputLine(
1683           currentResult,
1684           i == 0 || (i == 1 && emptyFirstSubString) ? preIndented : false,
1685           i == 0 ? newDepth : newDepth + 1,
1686           splitOperators[i],
1687           i,
1688           splitLine.startSubstringsIndexes,
1689           currentString.indexOf(currentResult));
1690         if (i != max - 1) {
1691           formattedSource.append(options.lineSeparatorSequence);
1692           increaseSplitDelta(options.lineSeparatorSequence.length);
1693         }
1694       }
1695     }
1696     if (result.length == splitOperators.length - 1) {
1697       int lastOperator = splitOperators[result.length];
1698       String lastOperatorString = operatorString(lastOperator);
1699       formattedSource.append(options.lineSeparatorSequence);
1700       increaseSplitDelta(options.lineSeparatorSequence.length);
1701
1702       if (breakLineBeforeOperator(lastOperator)) {
1703         dumpTab(depth + 1);
1704         if (lastOperator != 0) {
1705           if (insertSpaceBefore(lastOperator)) {
1706             formattedSource.append(' ');
1707             increaseSplitDelta(1);
1708           }
1709           formattedSource.append(lastOperatorString);
1710           increaseSplitDelta(lastOperatorString.length());
1711
1712           if (insertSpaceAfter(lastOperator) //                                 && lastOperator != TokenNameimplements
1713             && lastOperator != TokenNameextends) {
1714             //                                  && lastOperator != TokenNamethrows) {
1715             formattedSource.append(' ');
1716             increaseSplitDelta(1);
1717           }
1718         }
1719       }
1720     }
1721     if (placeOperatorBehind) {
1722       if (insertSpaceBefore(operator)) {
1723         formattedSource.append(' ');
1724         increaseSplitDelta(1);
1725       }
1726       formattedSource.append(operatorString);
1727       //increaseSplitDelta(operatorString.length());
1728     }
1729   }
1730
1731   /**
1732    * Pops the top statement of the stack if it is <code>token</code>
1733    */
1734   private int pop(int token) {
1735     int delta = 0;
1736     if ((constructionsCount > 0) && (constructions[constructionsCount - 1] == token)) {
1737       delta--;
1738       constructionsCount--;
1739     }
1740     return delta;
1741   }
1742
1743   /**
1744    * Pops the top statement of the stack if it is a <code>BLOCK</code> or a <code>NONINDENT_BLOCK</code>.
1745    */
1746   private int popBlock() {
1747     int delta = 0;
1748     if ((constructionsCount > 0)
1749       && ((constructions[constructionsCount - 1] == BLOCK) || (constructions[constructionsCount - 1] == NONINDENT_BLOCK))) {
1750       if (constructions[constructionsCount - 1] == BLOCK)
1751         delta--;
1752       constructionsCount--;
1753     }
1754     return delta;
1755   }
1756
1757   /**
1758    * Pops elements until the stack is empty or the top element is <code>token</code>.<br>
1759    * Does not remove <code>token</code> from the stack.
1760    * @param token the token to be left as the top of the stack
1761    */
1762   private int popExclusiveUntil(int token) {
1763     int delta = 0;
1764     int startCount = constructionsCount;
1765     for (int i = startCount - 1; i >= 0 && constructions[i] != token; i--) {
1766       if (constructions[i] != NONINDENT_BLOCK)
1767         delta--;
1768       constructionsCount--;
1769     }
1770     return delta;
1771   }
1772
1773   /**
1774    * Pops elements until the stack is empty or the top element is
1775    * a <code>BLOCK</code> or a <code>NONINDENT_BLOCK</code>.<br>
1776    * Does not remove it from the stack.
1777    */
1778   private int popExclusiveUntilBlock() {
1779     int startCount = constructionsCount;
1780     int delta = 0;
1781     for (int i = startCount - 1; i >= 0 && constructions[i] != BLOCK && constructions[i] != NONINDENT_BLOCK; i--) {
1782       constructionsCount--;
1783       delta--;
1784     }
1785     return delta;
1786   }
1787
1788   /**
1789    * Pops elements until the stack is empty or the top element is
1790    * a <code>BLOCK</code>, a <code>NONINDENT_BLOCK</code> or a <code>CASE</code>.<br>
1791    * Does not remove it from the stack.
1792    */
1793   private int popExclusiveUntilBlockOrCase() {
1794     int startCount = constructionsCount;
1795     int delta = 0;
1796     for (int i = startCount - 1;
1797       i >= 0 && constructions[i] != BLOCK && constructions[i] != NONINDENT_BLOCK && constructions[i] != TokenNamecase;
1798       i--) {
1799       constructionsCount--;
1800       delta--;
1801     }
1802     return delta;
1803   }
1804
1805   /**
1806    * Pops elements until the stack is empty or the top element is <code>token</code>.<br>
1807    * Removes <code>token</code> from the stack too.
1808    * @param token the token to remove from the stack
1809    */
1810   private int popInclusiveUntil(int token) {
1811     int startCount = constructionsCount;
1812     int delta = 0;
1813     for (int i = startCount - 1; i >= 0 && constructions[i] != token; i--) {
1814       if (constructions[i] != NONINDENT_BLOCK)
1815         delta--;
1816       constructionsCount--;
1817     }
1818     if (constructionsCount > 0) {
1819       if (constructions[constructionsCount - 1] != NONINDENT_BLOCK)
1820         delta--;
1821       constructionsCount--;
1822     }
1823     return delta;
1824   }
1825
1826   /**
1827    * Pops elements until the stack is empty or the top element is
1828    * a <code>BLOCK</code> or a <code>NONINDENT_BLOCK</code>.<br>
1829    * Does not remove it from the stack.
1830    */
1831   private int popInclusiveUntilBlock() {
1832     int startCount = constructionsCount;
1833     int delta = 0;
1834     for (int i = startCount - 1; i >= 0 && (constructions[i] != BLOCK && constructions[i] != NONINDENT_BLOCK); i--) {
1835       delta--;
1836       constructionsCount--;
1837     }
1838     if (constructionsCount > 0) {
1839       if (constructions[constructionsCount - 1] == BLOCK)
1840         delta--;
1841       constructionsCount--;
1842     }
1843     return delta;
1844   }
1845
1846   /** 
1847    * Pushes a block in the stack.<br>
1848    * Pushes a <code>BLOCK</code> if the stack is empty or if the top element is a <code>BLOCK</code>,
1849    * pushes <code>NONINDENT_BLOCK</code> otherwise.
1850    * Creates a new bigger array if the current one is full.
1851    */
1852   private int pushBlock() {
1853     int delta = 0;
1854     if (constructionsCount == constructions.length)
1855       System.arraycopy(constructions, 0, (constructions = new int[constructionsCount * 2]), 0, constructionsCount);
1856
1857     if ((constructionsCount == 0)
1858       || (constructions[constructionsCount - 1] == BLOCK)
1859       || (constructions[constructionsCount - 1] == NONINDENT_BLOCK)
1860       || (constructions[constructionsCount - 1] == TokenNamecase)) {
1861       delta++;
1862       constructions[constructionsCount++] = BLOCK;
1863     } else {
1864       constructions[constructionsCount++] = NONINDENT_BLOCK;
1865     }
1866     return delta;
1867   }
1868
1869   /** 
1870    * Pushes <code>token</code>.<br>
1871    * Creates a new bigger array if the current one is full.
1872    */
1873   private int pushControlStatement(int token) {
1874     if (constructionsCount == constructions.length)
1875       System.arraycopy(constructions, 0, (constructions = new int[constructionsCount * 2]), 0, constructionsCount);
1876     constructions[constructionsCount++] = token;
1877     return 1;
1878   }
1879
1880   private static boolean separateFirstArgumentOn(int currentToken) {
1881     //return (currentToken == TokenNameCOMMA || currentToken == TokenNameSEMICOLON);
1882     return currentToken != TokenNameif
1883       && currentToken != TokenNameLPAREN
1884       && currentToken != TokenNameNOT
1885       && currentToken != TokenNamewhile
1886       && currentToken != TokenNamefor
1887       && currentToken != TokenNameswitch;
1888   }
1889
1890   /**
1891    * Set the positions to map. The mapped positions should be retrieved using the
1892    * getMappedPositions() method.
1893    * @param positions int[]
1894    * @deprecated Set the positions to map using the format(String, int, int[]) method.
1895    * 
1896    * @see #getMappedPositions()
1897    */
1898   public void setPositionsToMap(int[] positions) {
1899     positionsToMap = positions;
1900     lineDelta = 0;
1901     globalDelta = 0;
1902     mappedPositions = new int[positions.length];
1903   }
1904
1905   /** 
1906    * Appends a space character to the current line buffer.
1907    */
1908   private void space() {
1909     currentLineBuffer.append(' ');
1910     increaseLineDelta(1);
1911   }
1912
1913   /**
1914    * Splits <code>stringToSplit</code> on the top level token<br>
1915    * If there are several identical token at the same level,
1916    * the string is cut into many pieces.
1917    * @return an object containing the operator and all the substrings
1918    * or null if the string cannot be split
1919    */
1920   public SplitLine split(String stringToSplit) {
1921     return split(stringToSplit, 0);
1922   }
1923
1924   /**
1925    * Splits <code>stringToSplit</code> on the top level token<br>
1926    * If there are several identical token at the same level,
1927    * the string is cut into many pieces.
1928    * @return an object containing the operator and all the substrings
1929    * or null if the string cannot be split
1930    */
1931   public SplitLine split(String stringToSplit, int offsetInGlobalLine) {
1932     /*
1933      * See http://dev.eclipse.org/bugs/show_bug.cgi?id=12540 and
1934      * http://dev.eclipse.org/bugs/show_bug.cgi?id=14387 
1935      */
1936     if (stringToSplit.indexOf("//$NON-NLS") != -1) { //$NON-NLS-1$
1937       return null;
1938     }
1939     // local variables
1940     int currentToken = 0;
1941     int splitTokenType = 0;
1942     int splitTokenDepth = Integer.MAX_VALUE;
1943     int splitTokenPriority = Integer.MAX_VALUE;
1944
1945     int[] substringsStartPositions = new int[10];
1946     // contains the start position of substrings
1947     int[] substringsEndPositions = new int[10];
1948     // contains the start position of substrings
1949     int substringsCount = 1; // index in the substringsStartPosition array
1950     int[] splitOperators = new int[10];
1951     // contains the start position of substrings
1952     int splitOperatorsCount = 0; // index in the substringsStartPosition array
1953     int[] openParenthesisPosition = new int[10];
1954     int openParenthesisPositionCount = 0;
1955     int position = 0;
1956     int lastOpenParenthesisPosition = -1;
1957     // used to remember the position of the 1st open parenthesis
1958     // needed for a pattern like: A.B(C); we want formatted like A.B( split C);
1959     // setup the scanner with a new source
1960     int lastCommentStartPosition = -1;
1961     // to remember the start position of the last comment
1962     int firstTokenOnLine = -1;
1963     // to remember the first token of the line
1964     int previousToken = -1;
1965     // to remember the previous token.
1966     splitScanner.setSource(stringToSplit.toCharArray());
1967
1968     try {
1969       // start the loop
1970       while (true) {
1971         // takes the next token
1972         try {
1973           if (currentToken != Scanner.TokenNameWHITESPACE)
1974             previousToken = currentToken;
1975           currentToken = splitScanner.getNextToken();
1976         } catch (InvalidInputException e) {
1977           if (!handleInvalidToken(e))
1978             throw e;
1979           currentToken = 0;
1980           // this value is not modify when an exception is raised.
1981         }
1982         if (currentToken == TokenNameEOF)
1983           break;
1984
1985         if (firstTokenOnLine == -1) {
1986           firstTokenOnLine = currentToken;
1987         }
1988         switch (currentToken) {
1989           case TokenNameRBRACE :
1990           case TokenNameRPAREN :
1991             if (openParenthesisPositionCount > 0) {
1992               if (openParenthesisPositionCount == 1 && lastOpenParenthesisPosition < openParenthesisPosition[0]) {
1993                 lastOpenParenthesisPosition = openParenthesisPosition[0];
1994               } else if (
1995                 (splitTokenDepth == Integer.MAX_VALUE)
1996                   || (splitTokenDepth > openParenthesisPositionCount && openParenthesisPositionCount == 1)) {
1997                 splitTokenType = 0;
1998                 splitTokenDepth = openParenthesisPositionCount;
1999                 splitTokenPriority = Integer.MAX_VALUE;
2000                 substringsStartPositions[0] = 0;
2001                 // better token means the whole line until now is the first substring
2002                 substringsCount = 1; // resets the count of substrings
2003                 substringsEndPositions[0] = openParenthesisPosition[0];
2004                 // substring ends on operator start
2005                 position = openParenthesisPosition[0];
2006                 // the string mustn't be cut before the closing parenthesis but after the opening one.
2007                 splitOperatorsCount = 1; // resets the count of split operators
2008                 splitOperators[0] = 0;
2009               }
2010               openParenthesisPositionCount--;
2011             }
2012             break;
2013           case TokenNameLBRACE :
2014           case TokenNameLPAREN :
2015             if (openParenthesisPositionCount == openParenthesisPosition.length) {
2016               System.arraycopy(
2017                 openParenthesisPosition,
2018                 0,
2019                 (openParenthesisPosition = new int[openParenthesisPositionCount * 2]),
2020                 0,
2021                 openParenthesisPositionCount);
2022             }
2023             openParenthesisPosition[openParenthesisPositionCount++] = splitScanner.currentPosition;
2024             if (currentToken == TokenNameLPAREN && previousToken == TokenNameRPAREN) {
2025               openParenthesisPosition[openParenthesisPositionCount - 1] = splitScanner.startPosition;
2026             }
2027             break;
2028           case TokenNameSEMICOLON : // ;
2029           case TokenNameCOMMA : // ,
2030           case TokenNameEQUAL : // =
2031             if (openParenthesisPositionCount < splitTokenDepth
2032               || (openParenthesisPositionCount == splitTokenDepth && splitTokenPriority > getTokenPriority(currentToken))) {
2033               // the current token is better than the one we currently have
2034               // (in level or in priority if same level)
2035               // reset the substringsCount
2036               splitTokenDepth = openParenthesisPositionCount;
2037               splitTokenType = currentToken;
2038               splitTokenPriority = getTokenPriority(currentToken);
2039               substringsStartPositions[0] = 0;
2040               // better token means the whole line until now is the first substring
2041
2042               if (separateFirstArgumentOn(firstTokenOnLine) && openParenthesisPositionCount > 0) {
2043                 substringsCount = 2; // resets the count of substrings
2044
2045                 substringsEndPositions[0] = openParenthesisPosition[splitTokenDepth - 1];
2046                 substringsStartPositions[1] = openParenthesisPosition[splitTokenDepth - 1];
2047                 substringsEndPositions[1] = splitScanner.startPosition;
2048                 splitOperatorsCount = 2; // resets the count of split operators
2049                 splitOperators[0] = 0;
2050                 splitOperators[1] = currentToken;
2051                 position = splitScanner.currentPosition;
2052                 // next substring will start from operator end
2053               } else {
2054                 substringsCount = 1; // resets the count of substrings
2055
2056                 substringsEndPositions[0] = splitScanner.startPosition;
2057                 // substring ends on operator start
2058                 position = splitScanner.currentPosition;
2059                 // next substring will start from operator end
2060                 splitOperatorsCount = 1; // resets the count of split operators
2061                 splitOperators[0] = currentToken;
2062               }
2063             } else {
2064               if ((openParenthesisPositionCount == splitTokenDepth && splitTokenPriority == getTokenPriority(currentToken))
2065                 && splitTokenType != TokenNameEQUAL
2066                 && currentToken != TokenNameEQUAL) {
2067                 // fix for 1FG0BCN: LFCOM:WIN98 - Missing one indentation after split
2068                 // take only the 1st = into account.
2069                 // if another token with the same priority is found,
2070                 // push the start position of the substring and
2071                 // push the token into the stack.
2072                 // create a new array object if the current one is full.
2073                 if (substringsCount == substringsStartPositions.length) {
2074                   System.arraycopy(
2075                     substringsStartPositions,
2076                     0,
2077                     (substringsStartPositions = new int[substringsCount * 2]),
2078                     0,
2079                     substringsCount);
2080                   System.arraycopy(
2081                     substringsEndPositions,
2082                     0,
2083                     (substringsEndPositions = new int[substringsCount * 2]),
2084                     0,
2085                     substringsCount);
2086                 }
2087                 if (splitOperatorsCount == splitOperators.length) {
2088                   System.arraycopy(splitOperators, 0, (splitOperators = new int[splitOperatorsCount * 2]), 0, splitOperatorsCount);
2089                 }
2090                 substringsStartPositions[substringsCount] = position;
2091                 substringsEndPositions[substringsCount++] = splitScanner.startPosition;
2092                 // substring ends on operator start
2093                 position = splitScanner.currentPosition;
2094                 // next substring will start from operator end
2095                 splitOperators[splitOperatorsCount++] = currentToken;
2096               }
2097             }
2098             break;
2099
2100           case TokenNameCOLON : // : (15.24)
2101             // see 1FK7C5R, we only split on a colon, when it is associated with a question-mark.
2102             // indeed it might appear also behind a case statement, and we do not to break at this point.
2103             if ((splitOperatorsCount == 0) || splitOperators[splitOperatorsCount - 1] != TokenNameQUESTION) {
2104               break;
2105             }
2106           case TokenNameextends :
2107             //                  case TokenNameimplements :
2108             //                  case TokenNamethrows :
2109
2110           case TokenNameDOT : // .
2111           case TokenNameMULTIPLY : // * (15.16.1)
2112           case TokenNameDIVIDE : // / (15.16.2)
2113           case TokenNameREMAINDER : // % (15.16.3)
2114           case TokenNamePLUS : // + (15.17, 15.17.2)
2115           case TokenNameMINUS : // - (15.17.2)
2116           case TokenNameLEFT_SHIFT : // << (15.18)
2117           case TokenNameRIGHT_SHIFT : // >> (15.18)
2118             //                          case TokenNameUNSIGNED_RIGHT_SHIFT : // >>> (15.18)
2119           case TokenNameLESS : // < (15.19.1)
2120           case TokenNameLESS_EQUAL : // <= (15.19.1)
2121           case TokenNameGREATER : // > (15.19.1)
2122           case TokenNameGREATER_EQUAL : // >= (15.19.1)
2123             //                          case TokenNameinstanceof : // instanceof
2124           case TokenNameEQUAL_EQUAL : // == (15.20, 15.20.1, 15.20.2, 15.20.3)
2125           case TokenNameNOT_EQUAL : // != (15.20, 15.20.1, 15.20.2, 15.20.3)
2126           case TokenNameAND : // & (15.21, 15.21.1, 15.21.2)
2127           case TokenNameOR : // | (15.21, 15.21.1, 15.21.2)
2128           case TokenNameXOR : // ^ (15.21, 15.21.1, 15.21.2)
2129           case TokenNameAND_AND : // && (15.22)
2130           case TokenNameOR_OR : // || (15.23)
2131           case TokenNameQUESTION : // ? (15.24)
2132           case TokenNameMULTIPLY_EQUAL : // *= (15.25.2)
2133           case TokenNameDIVIDE_EQUAL : // /= (15.25.2)
2134           case TokenNameREMAINDER_EQUAL : // %= (15.25.2)
2135           case TokenNamePLUS_EQUAL : // += (15.25.2)
2136           case TokenNameMINUS_EQUAL : // -= (15.25.2)
2137           case TokenNameLEFT_SHIFT_EQUAL : // <<= (15.25.2)
2138           case TokenNameRIGHT_SHIFT_EQUAL : // >>= (15.25.2)
2139             //                                  case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>= (15.25.2)
2140           case TokenNameAND_EQUAL : // &= (15.25.2)
2141           case TokenNameXOR_EQUAL : // ^= (15.25.2)
2142           case TokenNameOR_EQUAL : // |= (15.25.2)
2143
2144             if ((openParenthesisPositionCount < splitTokenDepth
2145               || (openParenthesisPositionCount == splitTokenDepth && splitTokenPriority > getTokenPriority(currentToken)))
2146               && !((currentToken == TokenNamePLUS || currentToken == TokenNameMINUS)
2147                 && (previousToken == TokenNameLBRACE || previousToken == TokenNameLBRACKET || splitScanner.startPosition == 0))) {
2148               // the current token is better than the one we currently have
2149               // (in level or in priority if same level)
2150               // reset the substringsCount
2151               splitTokenDepth = openParenthesisPositionCount;
2152               splitTokenType = currentToken;
2153               splitTokenPriority = getTokenPriority(currentToken);
2154               substringsStartPositions[0] = 0;
2155               // better token means the whole line until now is the first substring
2156
2157               if (separateFirstArgumentOn(firstTokenOnLine) && openParenthesisPositionCount > 0) {
2158                 substringsCount = 2; // resets the count of substrings
2159
2160                 substringsEndPositions[0] = openParenthesisPosition[splitTokenDepth - 1];
2161                 substringsStartPositions[1] = openParenthesisPosition[splitTokenDepth - 1];
2162                 substringsEndPositions[1] = splitScanner.startPosition;
2163                 splitOperatorsCount = 3; // resets the count of split operators
2164                 splitOperators[0] = 0;
2165                 splitOperators[1] = 0;
2166                 splitOperators[2] = currentToken;
2167                 position = splitScanner.currentPosition;
2168                 // next substring will start from operator end
2169               } else {
2170                 substringsCount = 1; // resets the count of substrings
2171
2172                 substringsEndPositions[0] = splitScanner.startPosition;
2173                 // substring ends on operator start
2174                 position = splitScanner.currentPosition;
2175                 // next substring will start from operator end
2176                 splitOperatorsCount = 2; // resets the count of split operators
2177                 splitOperators[0] = 0;
2178                 // nothing for first operand since operator will be inserted in front of the second operand
2179                 splitOperators[1] = currentToken;
2180
2181               }
2182             } else {
2183               if (openParenthesisPositionCount == splitTokenDepth && splitTokenPriority == getTokenPriority(currentToken)) {
2184                 // if another token with the same priority is found,
2185                 // push the start position of the substring and
2186                 // push the token into the stack.
2187                 // create a new array object if the current one is full.
2188                 if (substringsCount == substringsStartPositions.length) {
2189                   System.arraycopy(
2190                     substringsStartPositions,
2191                     0,
2192                     (substringsStartPositions = new int[substringsCount * 2]),
2193                     0,
2194                     substringsCount);
2195                   System.arraycopy(
2196                     substringsEndPositions,
2197                     0,
2198                     (substringsEndPositions = new int[substringsCount * 2]),
2199                     0,
2200                     substringsCount);
2201                 }
2202                 if (splitOperatorsCount == splitOperators.length) {
2203                   System.arraycopy(splitOperators, 0, (splitOperators = new int[splitOperatorsCount * 2]), 0, splitOperatorsCount);
2204                 }
2205                 substringsStartPositions[substringsCount] = position;
2206                 substringsEndPositions[substringsCount++] = splitScanner.startPosition;
2207                 // substring ends on operator start
2208                 position = splitScanner.currentPosition;
2209                 // next substring will start from operator end
2210                 splitOperators[splitOperatorsCount++] = currentToken;
2211               }
2212             }
2213           default :
2214             break;
2215         }
2216         if (isComment(currentToken)) {
2217           lastCommentStartPosition = splitScanner.startPosition;
2218         } else {
2219           lastCommentStartPosition = -1;
2220         }
2221       }
2222     } catch (InvalidInputException e) {
2223       return null;
2224     }
2225     // if the string cannot be split, return null.
2226     if (splitOperatorsCount == 0)
2227       return null;
2228
2229     // ## SPECIAL CASES BEGIN
2230     if (((splitOperatorsCount == 2
2231       && splitOperators[1] == TokenNameDOT
2232       && splitTokenDepth == 0
2233       && lastOpenParenthesisPosition > -1)
2234       || (splitOperatorsCount > 2
2235         && splitOperators[1] == TokenNameDOT
2236         && splitTokenDepth == 0
2237         && lastOpenParenthesisPosition > -1
2238         && lastOpenParenthesisPosition <= options.maxLineLength)
2239       || (separateFirstArgumentOn(firstTokenOnLine) && splitTokenDepth > 0 && lastOpenParenthesisPosition > -1))
2240       && (lastOpenParenthesisPosition < splitScanner.source.length && splitScanner.source[lastOpenParenthesisPosition] != ')')) {
2241       // fix for 1FH4J2H: LFCOM:WINNT - Formatter - Empty parenthesis should not be broken on two lines
2242       // only one split on a top level .
2243       // or more than one split on . and substring before open parenthesis fits one line.
2244       // or split inside parenthesis and first token is not a for/while/if
2245       SplitLine sl = split(stringToSplit.substring(lastOpenParenthesisPosition), lastOpenParenthesisPosition);
2246       if (sl == null || sl.operators[0] != TokenNameCOMMA) {
2247         // trim() is used to remove the extra blanks at the end of the substring. See PR 1FGYPI1
2248         return new SplitLine(
2249           new int[] { 0, 0 },
2250           new String[] {
2251             stringToSplit.substring(0, lastOpenParenthesisPosition).trim(),
2252             stringToSplit.substring(lastOpenParenthesisPosition)},
2253           new int[] { offsetInGlobalLine, lastOpenParenthesisPosition + offsetInGlobalLine });
2254       } else {
2255         // right substring can be split and is split on comma
2256         // copy substrings and operators
2257         // except if the 1st string is empty.
2258         int startIndex = (sl.substrings[0].length() == 0) ? 1 : 0;
2259         int subStringsLength = sl.substrings.length + 1 - startIndex;
2260         String[] result = new String[subStringsLength];
2261         int[] startIndexes = new int[subStringsLength];
2262         int operatorsLength = sl.operators.length + 1 - startIndex;
2263         int[] operators = new int[operatorsLength];
2264
2265         result[0] = stringToSplit.substring(0, lastOpenParenthesisPosition);
2266         operators[0] = 0;
2267
2268         System.arraycopy(sl.startSubstringsIndexes, startIndex, startIndexes, 1, subStringsLength - 1);
2269         for (int i = subStringsLength - 1; i >= 0; i--) {
2270           startIndexes[i] += offsetInGlobalLine;
2271         }
2272         System.arraycopy(sl.substrings, startIndex, result, 1, subStringsLength - 1);
2273         System.arraycopy(sl.operators, startIndex, operators, 1, operatorsLength - 1);
2274
2275         return new SplitLine(operators, result, startIndexes);
2276       }
2277     }
2278     // if the last token is a comment and the substring before the comment fits on a line,
2279     // split before the comment and return the result.
2280     if (lastCommentStartPosition > -1 && lastCommentStartPosition < options.maxLineLength && splitTokenPriority > 50) {
2281       int end = lastCommentStartPosition;
2282       int start = lastCommentStartPosition;
2283       if (stringToSplit.charAt(end - 1) == ' ') {
2284         end--;
2285       }
2286       if (start != end && stringToSplit.charAt(start) == ' ') {
2287         start++;
2288       }
2289       return new SplitLine(
2290         new int[] { 0, 0 },
2291         new String[] { stringToSplit.substring(0, end), stringToSplit.substring(start)},
2292         new int[] { 0, start });
2293     }
2294     if (position != stringToSplit.length()) {
2295       if (substringsCount == substringsStartPositions.length) {
2296         System.arraycopy(
2297           substringsStartPositions,
2298           0,
2299           (substringsStartPositions = new int[substringsCount * 2]),
2300           0,
2301           substringsCount);
2302         System.arraycopy(substringsEndPositions, 0, (substringsEndPositions = new int[substringsCount * 2]), 0, substringsCount);
2303       }
2304       // avoid empty extra substring, e.g. line terminated with a semi-colon
2305       substringsStartPositions[substringsCount] = position;
2306       substringsEndPositions[substringsCount++] = stringToSplit.length();
2307     }
2308     if (splitOperatorsCount == splitOperators.length) {
2309       System.arraycopy(splitOperators, 0, (splitOperators = new int[splitOperatorsCount * 2]), 0, splitOperatorsCount);
2310     }
2311     splitOperators[splitOperatorsCount] = 0;
2312
2313     // the last element of the stack is the position of the end of StringToSPlit
2314     // +1 because the substring method excludes the last character
2315     String[] result = new String[substringsCount];
2316     for (int i = 0; i < substringsCount; i++) {
2317       int start = substringsStartPositions[i];
2318       int end = substringsEndPositions[i];
2319       if (stringToSplit.charAt(start) == ' ') {
2320         start++;
2321         substringsStartPositions[i]++;
2322       }
2323       if (end != start && stringToSplit.charAt(end - 1) == ' ') {
2324         end--;
2325       }
2326       result[i] = stringToSplit.substring(start, end);
2327       substringsStartPositions[i] += offsetInGlobalLine;
2328     }
2329     if (splitOperatorsCount > substringsCount) {
2330       System.arraycopy(substringsStartPositions, 0, (substringsStartPositions = new int[splitOperatorsCount]), 0, substringsCount);
2331       System.arraycopy(substringsEndPositions, 0, (substringsEndPositions = new int[splitOperatorsCount]), 0, substringsCount);
2332       for (int i = substringsCount; i < splitOperatorsCount; i++) {
2333         substringsStartPositions[i] = position;
2334         substringsEndPositions[i] = position;
2335       }
2336       System.arraycopy(splitOperators, 0, (splitOperators = new int[splitOperatorsCount]), 0, splitOperatorsCount);
2337     } else {
2338       System.arraycopy(substringsStartPositions, 0, (substringsStartPositions = new int[substringsCount]), 0, substringsCount);
2339       System.arraycopy(substringsEndPositions, 0, (substringsEndPositions = new int[substringsCount]), 0, substringsCount);
2340       System.arraycopy(splitOperators, 0, (splitOperators = new int[substringsCount]), 0, substringsCount);
2341     }
2342     SplitLine splitLine = new SplitLine(splitOperators, result, substringsStartPositions);
2343     return splitLine;
2344   }
2345
2346   private void updateMappedPositions(int startPosition) {
2347     if (positionsToMap == null) {
2348       return;
2349     }
2350     char[] source = scanner.source;
2351     int sourceLength = source.length;
2352     while (indexToMap < positionsToMap.length && positionsToMap[indexToMap] <= startPosition) {
2353       int posToMap = positionsToMap[indexToMap];
2354       if (posToMap < 0 || posToMap >= sourceLength) {
2355         // protection against out of bounds position
2356         if (posToMap == sourceLength) {
2357           mappedPositions[indexToMap] = formattedSource.length();
2358         }
2359         indexToMap = positionsToMap.length; // no more mapping
2360         return;
2361       }
2362       if (CharOperation.isWhitespace(source[posToMap])) {
2363         mappedPositions[indexToMap] = startPosition + globalDelta + lineDelta;
2364       } else {
2365         if (posToMap == sourceLength - 1) {
2366           mappedPositions[indexToMap] = startPosition + globalDelta + lineDelta;
2367         } else {
2368           mappedPositions[indexToMap] = posToMap + globalDelta + lineDelta;
2369         }
2370       }
2371       indexToMap++;
2372     }
2373   }
2374
2375   private void updateMappedPositionsWhileSplitting(int startPosition, int endPosition) {
2376     if (mappedPositions == null || mappedPositions.length == indexInMap)
2377       return;
2378
2379     while (indexInMap < mappedPositions.length
2380       && startPosition <= mappedPositions[indexInMap]
2381       && mappedPositions[indexInMap] < endPosition
2382       && indexInMap < indexToMap) {
2383       mappedPositions[indexInMap] += splitDelta;
2384       indexInMap++;
2385     }
2386   }
2387
2388   private int getLength(String s, int tabDepth) {
2389     int length = 0;
2390     for (int i = 0; i < tabDepth; i++) {
2391       length += options.tabSize;
2392     }
2393     for (int i = 0, max = s.length(); i < max; i++) {
2394       char currentChar = s.charAt(i);
2395       switch (currentChar) {
2396         case '\t' :
2397           length += options.tabSize;
2398           break;
2399         default :
2400           length++;
2401       }
2402     }
2403     return length;
2404   }
2405
2406   /** 
2407   * Sets the initial indentation level
2408   * @param indentationLevel new indentation level
2409   * 
2410   * @deprecated
2411   */
2412   public void setInitialIndentationLevel(int newIndentationLevel) {
2413     this.initialIndentationLevel = currentLineIndentationLevel = indentationLevel = newIndentationLevel;
2414   }
2415 }