/*******************************************************************************
 * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 *
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 ******************************************************************************/
package net.sourceforge.phpdt.internal.formatter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;

import javax.swing.text.html.Option;

import net.sourceforge.phpdt.core.ICodeFormatter;
import net.sourceforge.phpdt.core.compiler.CharOperation;
import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
import net.sourceforge.phpdt.core.compiler.InvalidInputException;
import net.sourceforge.phpdt.internal.compiler.ConfigurableOption;
import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
import net.sourceforge.phpdt.internal.formatter.impl.FormatterOptions;
import net.sourceforge.phpdt.internal.formatter.impl.SplitLine;

/**
 * <h2>How to format a piece of code ?</h2>
 * <ul>
 * <li>Create an instance of <code>CodeFormatter</code>
 * <li>Use the method <code>void format(aString)</code> on this instance to
 * format <code>aString</code>. It will return the formatted string.
 * </ul>
 */
public class CodeFormatter implements ITerminalSymbols, ICodeFormatter {
	// IContentFormatterExtension {
	public FormatterOptions options;

	/**
	 * Represents a block in the <code>constructions</code> stack.
	 */
	public static final int BLOCK = ITerminalSymbols.TokenNameLBRACE;

	/**
	 * Represents a block following a control statement in the
	 * <code>constructions</code> stack.
	 */
	public static final int NONINDENT_BLOCK = -100;

	/**
	 * Contains the formatted output.
	 */
	StringBuffer formattedSource;

	/**
	 * Contains the current line. <br>
	 * Will be dumped at the next "newline"
	 */
	StringBuffer currentLineBuffer;

	/**
	 * Used during the formatting to get each token.
	 */
	Scanner scanner;

	/**
	 * Contains the tokens responsible for the current indentation level and the
	 * blocks not closed yet.
	 */
	private int[] constructions;

	/**
	 * Index in the <code>constructions</code> array.
	 */
	private int constructionsCount;

	/**
	 * Level of indentation of the current token (number of tab char put in
	 * front of it).
	 */
	private int indentationLevel;

	/**
	 * Regular level of indentation of all the lines
	 */
	private int initialIndentationLevel;

	/**
	 * Used to split a line.
	 */
	Scanner splitScanner;

	/**
	 * To remember the offset between the beginning of the line and the
	 * beginning of the comment.
	 */
	int currentCommentOffset;

	int currentLineIndentationLevel;

	int maxLineSize = 30;

	private boolean containsOpenCloseBraces;

	private int indentationLevelForOpenCloseBraces;

	/**
	 * Collections of positions to map
	 */
	private int[] positionsToMap;

	/**
	 * Collections of mapped positions
	 */
	private int[] mappedPositions;

	private int indexToMap;

	private int indexInMap;

	private int globalDelta;

	private int lineDelta;

	private int splitDelta;

	private int beginningOfLineIndex;

	private int multipleLineCommentCounter;

	/**
	 * Creates a new instance of Code Formatter using the given settings.
	 * 
	 * @deprecated backport 1.0 internal functionality
	 */
	public CodeFormatter(ConfigurableOption[] settings) {
		this(convertConfigurableOptions(settings));
	}

	/**
	 * Creates a new instance of Code Formatter using the FormattingOptions
	 * object given as argument
	 * 
	 * @deprecated Use CodeFormatter(ConfigurableOption[]) instead
	 */
	public CodeFormatter() {
		this((Map) null);
	}

	/**
	 * Creates a new instance of Code Formatter using the given settings.
	 */
	public CodeFormatter(Map settings) {
		// initialize internal state
		constructionsCount = 0;
		constructions = new int[10];
		currentLineIndentationLevel = indentationLevel = initialIndentationLevel;
		currentCommentOffset = -1;
		// initialize primary and secondary scanners
		scanner = new Scanner(true /* comment */
		, true /* whitespace */
		, false /* nls */
		, false /* assert */
		, true, /* tokenizeStrings */
		null, null, true /* taskCaseSensitive */); // regular scanner for
													// forming lines
		scanner.recordLineSeparator = true;
		scanner.ignorePHPOneLiner = true;
		// to remind of the position of the beginning of the line.
		splitScanner = new Scanner(true /* comment */
		, true /* whitespace */
		, false /* nls */
		, false /* assert */
		, true, /* tokenizeStrings */
		null, null, true /* taskCaseSensitive */);
		splitScanner.ignorePHPOneLiner = true;
		// secondary scanner to split long lines formed by primary scanning
		// initialize current line buffer
		currentLineBuffer = new StringBuffer();
		this.options = new FormatterOptions(settings);
	}

	/**
	 * Returns true if a lineSeparator has to be inserted before
	 * <code>operator</code> false otherwise.
	 */
	private static boolean breakLineBeforeOperator(int operator) {
		switch (operator) {
		case TokenNameCOMMA:
		case TokenNameSEMICOLON:
		case TokenNameEQUAL:
			return false;
		default:
			return true;
		}
	}

	/**
	 * @deprecated backport 1.0 internal functionality
	 */
	private static Map convertConfigurableOptions(ConfigurableOption[] settings) {
		Hashtable options = new Hashtable(10);
		for (int i = 0; i < settings.length; i++) {
			if (settings[i].getComponentName().equals(
					CodeFormatter.class.getName())) {
				String optionName = settings[i].getOptionName();
				int valueIndex = settings[i].getCurrentValueIndex();
				if (optionName.equals("newline.openingBrace")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.newline.openingBrace", valueIndex == 0 ? "insert" : "do not insert"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				} else if (optionName.equals("newline.controlStatement")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.newline.controlStatement", valueIndex == 0 ? "insert" : "do not insert"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				} else if (optionName.equals("newline.clearAll")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.newline.clearAll", valueIndex == 0 ? "clear all" : "preserve one"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				} else if (optionName.equals("newline.elseIf")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.newline.elseIf", valueIndex == 0 ? "do not insert" : "insert"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				} else if (optionName.equals("newline.emptyBlock")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.newline.emptyBlock", valueIndex == 0 ? "insert" : "do not insert"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				} else if (optionName.equals("lineSplit")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.lineSplit", String.valueOf(valueIndex)); //$NON-NLS-1$ //$NON-NLS-2$
				} else if (optionName.equals("style.assignment")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.style.assignment", valueIndex == 0 ? "compact" : "normal"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				} else if (optionName.equals("tabulation.char")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.tabulation.char", valueIndex == 0 ? "tab" : "space"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				} else if (optionName.equals("tabulation.size")) { //$NON-NLS-1$
					options
							.put(
									"net.sourceforge.phpdt.core.formatter.tabulation.size", String.valueOf(valueIndex)); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		}
		return options;
	}

	/**
	 * Returns the end of the source code.
	 */
	private final String copyRemainingSource() {
		char str[] = scanner.source;
		int startPosition = scanner.startPosition;
		int length = str.length - startPosition;
		StringBuffer bufr = new StringBuffer(length);
		if (startPosition < str.length) {
			bufr.append(str, startPosition, length);
		}
		return (bufr.toString());
	}

	/**
	 * Inserts <code>tabCount</code> tab character or their equivalent number
	 * of spaces.
	 */
	private void dumpTab(int tabCount) {
		if (options.indentWithTab) {
			for (int j = 0; j < tabCount; j++) {
				formattedSource.append('\t');
				increaseSplitDelta(1);
			}
		} else {
			for (int i = 0, max = options.tabSize * tabCount; i < max; i++) {
				formattedSource.append(' ');
				increaseSplitDelta(1);
			}
		}
	}

	/**
	 * Dumps <code>currentLineBuffer</code> into the formatted string.
	 */
	private void flushBuffer() {
		String currentString = currentLineBuffer.toString();
		splitDelta = 0;
		beginningOfLineIndex = formattedSource.length();
		if (containsOpenCloseBraces) {
			containsOpenCloseBraces = false;
			outputLine(currentString, false,
					indentationLevelForOpenCloseBraces, 0, -1, null, 0);
			indentationLevelForOpenCloseBraces = currentLineIndentationLevel;
		} else {
			outputLine(currentString, false, currentLineIndentationLevel, 0,
					-1, null, 0);
		}
		int scannerSourceLength = scanner.source.length;
		if ((scannerSourceLength > 2)
				&& (scanner.startPosition < scannerSourceLength)) {
			if (scanner.source[scannerSourceLength - 1] == '\n'
					&& scanner.source[scannerSourceLength - 2] == '\r') {
				formattedSource.append(options.lineSeparatorSequence);
				increaseGlobalDelta(options.lineSeparatorSequence.length - 2);
			} else if (scanner.source[scannerSourceLength - 1] == '\n') {
				formattedSource.append(options.lineSeparatorSequence);
				increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
			} else if (scanner.source[scannerSourceLength - 1] == '\r') {
				formattedSource.append(options.lineSeparatorSequence);
				increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
			}
		}
		updateMappedPositions(scanner.startPosition);
	}

	/**
	 * Formats the input string.
	 */
	private void format() {
		int token = 0;
		int previousToken = 0;
		int previousCompilableToken = 0;
		int indentationOffset = 0;
		int newLinesInWhitespace = 0;
		// number of new lines in the previous whitespace token
		// (used to leave blank lines before comments)
		int pendingNewLines = 0;
		boolean expectingOpenBrace = false;
		boolean clearNonBlockIndents = false;
		// true if all indentations till the 1st { (usefull after } or ;)
		boolean pendingSpace = true;
		boolean pendingNewlineAfterParen = false;
		// true when a cr is to be put after a ) (in conditional statements)
		boolean inAssignment = false;
		boolean inArrayAssignment = false;
		boolean inThrowsClause = false;
		boolean inClassOrInterfaceHeader = false;
		int dollarBraceCount = 0;
		// openBracketCount is used to count the number of open brackets not
		// closed
		// yet.
		int openBracketCount = 0;
		int unarySignModifier = 0;
		// openParenthesis[0] is used to count the parenthesis not belonging to
		// a
		// condition
		// (eg foo();). parenthesis in for (...) are count elsewhere in the
		// array.
		int openParenthesisCount = 1;
		int[] openParenthesis = new int[10];
		// tokenBeforeColon is used to know what token goes along with the
		// current
		// :
		// it can be case or ?
		int tokenBeforeColonCount = 0;
		int[] tokenBeforeColon = new int[10];
		constructionsCount = 0; // initializes the constructions count.
		// contains DO if in a DO..WHILE statement, UNITIALIZED otherwise.
		int nlicsToken = 0;
		// fix for 1FF17XY: LFCOM:ALL - Format problem on not matching } and
		// else
		boolean specialElse = false;
		// OPTION (IndentationLevel): initial indentation level may be non-zero.
		currentLineIndentationLevel += constructionsCount;
		// An InvalidInputException exception might cause the termination of
		// this
		// loop.
		int arrayDeclarationCount = 0;
		int[] arrayDeclarationParenthesis = new int[10];
		try {
			while (true) {
				// Get the next token. Catch invalid input and output it
				// with minimal formatting, also catch end of input and
				// exit the loop.
				try {
					token = scanner.getNextToken();
					if (Scanner.DEBUG) {
						int currentEndPosition = scanner
								.getCurrentTokenEndPosition();
						int currentStartPosition = scanner
								.getCurrentTokenStartPosition();
						System.out.print(currentStartPosition + ","
								+ currentEndPosition + ": ");
						System.out.println(scanner.toStringAction(token));
					}
					// Patch for line comment
					// See PR http://dev.eclipse.org/bugs/show_bug.cgi?id=23096
					if (token == ITerminalSymbols.TokenNameCOMMENT_LINE) {
						int length = scanner.currentPosition;
						loop: for (int index = length - 1; index >= 0; index--) {
							switch (scanner.source[index]) {
							case '\r':
							case '\n':
								scanner.currentPosition--;
								break;
							default:
								break loop;
							}
						}
					}
				} catch (InvalidInputException e) {
					if (!handleInvalidToken(e)) {
						throw e;
					}
					token = 0;
				}
				if (token == Scanner.TokenNameEOF) {
					break;
				} else if (token == Scanner.TokenNameHEREDOC) {
					// no indentation for heredocs and HTML !
					outputCurrentTokenWithoutIndent(Scanner.TokenNameHEREDOC, 0);
					continue;
				} else if (token == Scanner.TokenNameINLINE_HTML) {
					// no indentation for heredocs and HTML !
					int newLineCount = 1;
					if (scanner.startPosition == 0) {
						newLineCount = 0;
					}
					outputCurrentTokenWithoutIndent(
							Scanner.TokenNameINLINE_HTML, newLineCount);
					int srcLen = scanner.source.length;
					if (scanner.currentPosition < srcLen - 1) {
						newLine(1);
					}
					continue;
				}
				/*
				 * ## MODIFYING the indentation level before generating new
				 * lines and indentation in the output string
				 */
				// Removes all the indentations made by statements not followed
				// by a
				// block
				// except if the current token is ELSE, CATCH or if we are in a
				// switch/case
				if (clearNonBlockIndents
						&& (token != Scanner.TokenNameWHITESPACE)) {
					switch (token) {
					case TokenNameelse:
						if (constructionsCount > 0
								&& constructions[constructionsCount - 1] == TokenNameelse) {
							pendingNewLines = 1;
							specialElse = true;
						}
						indentationLevel += popInclusiveUntil(TokenNameif);
						break;
					// case TokenNamecatch :
					// indentationLevel += popInclusiveUntil(TokenNamecatch);
					// break;
					// case TokenNamefinally :
					// indentationLevel += popInclusiveUntil(TokenNamecatch);
					// break;
					case TokenNamewhile:
						if (nlicsToken == TokenNamedo) {
							indentationLevel += pop(TokenNamedo);
							break;
						}
					default:
						indentationLevel += popExclusiveUntilBlockOrCase();
						// clear until a CASE, DEFAULT or BLOCK is encountered.
						// Thus, the indentationLevel is correctly cleared
						// either
						// in a switch/case statement or in any other situation.
					}
					clearNonBlockIndents = false;
				}
				// returns to the indentation level created by the SWITCH
				// keyword
				// if the current token is a CASE or a DEFAULT
				if (token == TokenNamecase || token == TokenNamedefault) {
					indentationLevel += pop(TokenNamecase);
				}
				// if (token == Scanner.TokenNamethrows) {
				// inThrowsClause = true;
				// }
				if ((token == Scanner.TokenNameclass || token == Scanner.TokenNameinterface)
						&& previousToken != Scanner.TokenNameDOT) {
					inClassOrInterfaceHeader = true;
				}
				/*
				 * ## APPEND newlines and indentations to the output string
				 */
				// Do not add a new line between ELSE and IF, if the option
				// elseIfOnSameLine is true.
				// Fix for 1ETLWPZ: IVJCOM:ALL - incorrect "else if" formatting
				// if (pendingNewlineAfterParen
				// && previousCompilableToken == TokenNameelse
				// && token == TokenNameif
				// && options.compactElseIfMode) {
				// pendingNewlineAfterParen = false;
				// pendingNewLines = 0;
				// indentationLevel += pop(TokenNameelse);
				// // because else if is now one single statement,
				// // the indentation level after it is increased by one and not
				// by 2
				// // (else = 1 indent, if = 1 indent, but else if = 1 indent,
				// not 2).
				// }
				// Add a newline & indent to the formatted source string if
				// a for/if-else/while statement was scanned and there is no
				// block
				// following it.
				pendingNewlineAfterParen = pendingNewlineAfterParen
						|| (previousCompilableToken == TokenNameRPAREN && token == TokenNameLBRACE);
				if (pendingNewlineAfterParen
						&& token != Scanner.TokenNameWHITESPACE) {
					pendingNewlineAfterParen = false;
					// Do to add a newline & indent sequence if the current
					// token is an
					// open brace or a period or if the current token is a
					// semi-colon and
					// the
					// previous token is a close paren.
					// add a new line if a parenthesis belonging to a for()
					// statement
					// has been closed and the current token is not an opening
					// brace
					if (token != TokenNameLBRACE && !isComment(token)
							// to avoid adding new line between else and a
							// comment
							&& token != TokenNameDOT
							&& !(previousCompilableToken == TokenNameRPAREN && token == TokenNameSEMICOLON)) {
						newLine(1);
						currentLineIndentationLevel = indentationLevel;
						pendingNewLines = 0;
						pendingSpace = false;
					} else {
						if (token == TokenNameLBRACE
								&& options.newLineBeforeOpeningBraceMode) {
							newLine(1);
							if (constructionsCount > 0
									&& constructions[constructionsCount - 1] != BLOCK
									&& constructions[constructionsCount - 1] != NONINDENT_BLOCK) {
								currentLineIndentationLevel = indentationLevel - 1;
							} else {
								currentLineIndentationLevel = indentationLevel;
							}
							pendingNewLines = 0;
							pendingSpace = false;
						}
					}
				}
				if (token == TokenNameLBRACE
						&& options.newLineBeforeOpeningBraceMode
						&& constructionsCount > 0
						&& constructions[constructionsCount - 1] == TokenNamedo) {
					newLine(1);
					currentLineIndentationLevel = indentationLevel - 1;
					pendingNewLines = 0;
					pendingSpace = false;
				}
				// see PR 1G5G8EC
				if (token == TokenNameLBRACE && inThrowsClause) {
					inThrowsClause = false;
					if (options.newLineBeforeOpeningBraceMode) {
						newLine(1);
						currentLineIndentationLevel = indentationLevel;
						pendingNewLines = 0;
						pendingSpace = false;
					}
				}
				// see PR 1G5G82G
				if (token == TokenNameLBRACE && inClassOrInterfaceHeader) {
					inClassOrInterfaceHeader = false;
					if (options.newLineBeforeOpeningBraceMode) {
						newLine(1);
						currentLineIndentationLevel = indentationLevel;
						pendingNewLines = 0;
						pendingSpace = false;
					}
				}
				// don't linebreak empty array declarations
				if (token == TokenNameRPAREN && arrayDeclarationCount > 0) {
					if (previousCompilableToken == TokenNameLPAREN) {
						pendingNewLines = 0;
					}
				}
				// Add pending new lines to the formatted source string.
				// Note: pending new lines are not added if the current token
				// is a single line comment or whitespace.
				// if the comment is between parenthesis, there is no blank line
				// preservation
				// (if it's a one-line comment, a blank line is added after it).
				if (((pendingNewLines > 0 && (!isComment(token)))
						|| (newLinesInWhitespace > 0 && (openParenthesisCount <= 1 && isComment(token)))
						|| (previousCompilableToken == TokenNameLBRACE && token == TokenNameRBRACE) || (newLinesInWhitespace > 0 && previousCompilableToken == TokenNameDOT))
						&& token != Scanner.TokenNameWHITESPACE) {
					// Do not add newline & indent between an adjoining close
					// brace and
					// close paren. Anonymous inner classes may use this form.
					boolean closeBraceAndCloseParen = previousToken == TokenNameRBRACE
							&& token == TokenNameRPAREN;
					// OPTION (NewLineInCompoundStatement): do not add newline &
					// indent
					// between close brace and else, (do) while, catch, and
					// finally if
					// newlineInCompoundStatement is true.
					boolean nlicsOption = previousToken == TokenNameRBRACE
							&& !options.newlineInControlStatementMode
							&& (token == TokenNameelse
									|| (token == TokenNamewhile && nlicsToken == TokenNamedo)
									|| token == TokenNamecatch || token == TokenNamefinally);
					// Do not add a newline & indent between a close brace and
					// semi-colon.
					boolean semiColonAndCloseBrace = previousToken == TokenNameRBRACE
							&& token == TokenNameSEMICOLON;
					// Do not add a new line & indent between a multiline
					// comment and a
					// opening brace
					boolean commentAndOpenBrace = previousToken == Scanner.TokenNameCOMMENT_BLOCK
							&& token == TokenNameLBRACE;
					// Do not add a newline & indent between a close brace and a
					// colon
					// (in array assignments, for example).
					boolean commaAndCloseBrace = previousToken == TokenNameRBRACE
							&& token == TokenNameCOMMA;
					// Add a newline and indent, if appropriate.
					if (specialElse
							|| (!commentAndOpenBrace
									&& !closeBraceAndCloseParen && !nlicsOption
									&& !semiColonAndCloseBrace && !commaAndCloseBrace)) {
						// if clearAllBlankLinesMode=false, leaves the blank
						// lines
						// inserted by the user
						// if clearAllBlankLinesMode=true, removes all of then
						// and insert only blank lines required by the
						// formatting.
						if (!options.clearAllBlankLinesMode) {
							// (isComment(token))
							pendingNewLines = (pendingNewLines < newLinesInWhitespace) ? newLinesInWhitespace
									: pendingNewLines;
							pendingNewLines = (pendingNewLines > 2) ? 2
									: pendingNewLines;
						}
						if (previousCompilableToken == TokenNameLBRACE
								&& token == TokenNameRBRACE) {
							containsOpenCloseBraces = true;
							indentationLevelForOpenCloseBraces = currentLineIndentationLevel;
							if (isComment(previousToken)) {
								newLine(pendingNewLines);
							} else {
								/*
								 * if (!(constructionsCount > 1 &&
								 * constructions[constructionsCount-1] ==
								 * NONINDENT_BLOCK &&
								 * (constructions[constructionsCount-2] ==
								 * TokenNamefor
								 */
								if (options.newLineInEmptyBlockMode) {
									if (inArrayAssignment) {
										newLine(1); // array assigment with an
													// empty block
									} else {
										newLine(pendingNewLines);
									}
								}
								// }
							}
						} else {
							// see PR 1FKKC3U: LFCOM:WINNT - Format problem with
							// a comment
							// before the ';'
							if (!((previousToken == Scanner.TokenNameCOMMENT_BLOCK || previousToken == Scanner.TokenNameCOMMENT_PHPDOC) && token == TokenNameSEMICOLON)) {
								newLine(pendingNewLines);
							}
						}
						if (((previousCompilableToken == TokenNameSEMICOLON)
								|| (previousCompilableToken == TokenNameLBRACE)
								|| (previousCompilableToken == TokenNameRBRACE) || (isComment(previousToken)))
								&& (token == TokenNameRBRACE)) {
							indentationOffset = -1;
							indentationLevel += popExclusiveUntilBlock();
						}
						if (previousToken == Scanner.TokenNameCOMMENT_LINE
								&& inAssignment) {
							// PR 1FI5IPO
							currentLineIndentationLevel++;
						} else {
							currentLineIndentationLevel = indentationLevel
									+ indentationOffset;
						}
						pendingSpace = false;
						indentationOffset = 0;
					}
					pendingNewLines = 0;
					newLinesInWhitespace = 0;
					specialElse = false;
					if (nlicsToken == TokenNamedo && token == TokenNamewhile) {
						nlicsToken = 0;
					}
				}
				boolean phpTagAndWhitespace = previousToken == TokenNameINLINE_HTML
						&& token == TokenNameWHITESPACE;
				switch (token) {
				// case TokenNameDOLLAR :
				// dollarBraceCount++;
				// break;
				case TokenNameelse:
					// case TokenNamefinally :
					expectingOpenBrace = true;
					pendingNewlineAfterParen = true;
					indentationLevel += pushControlStatement(token);
					break;
				case TokenNamecase:
				case TokenNamedefault:
					if (tokenBeforeColonCount == tokenBeforeColon.length) {
						System
								.arraycopy(
										tokenBeforeColon,
										0,
										(tokenBeforeColon = new int[tokenBeforeColonCount * 2]),
										0, tokenBeforeColonCount);
					}
					tokenBeforeColon[tokenBeforeColonCount++] = TokenNamecase;
					indentationLevel += pushControlStatement(TokenNamecase);
					break;
				case TokenNameQUESTION:
					if (tokenBeforeColonCount == tokenBeforeColon.length) {
						System
								.arraycopy(
										tokenBeforeColon,
										0,
										(tokenBeforeColon = new int[tokenBeforeColonCount * 2]),
										0, tokenBeforeColonCount);
					}
					tokenBeforeColon[tokenBeforeColonCount++] = token;
					break;
				case TokenNameswitch:
				case TokenNamefor:
				case TokenNameforeach:
				case TokenNameif:
				case TokenNamewhile:
					if (openParenthesisCount == openParenthesis.length) {
						System
								.arraycopy(
										openParenthesis,
										0,
										(openParenthesis = new int[openParenthesisCount * 2]),
										0, openParenthesisCount);
					}
					openParenthesis[openParenthesisCount++] = 0;
					expectingOpenBrace = true;
					indentationLevel += pushControlStatement(token);
					break;
				case TokenNametry:
					pendingNewlineAfterParen = true;
				case TokenNamecatch:
					// several CATCH statements can be contiguous.
					// a CATCH is encountered pop until first CATCH (if a CATCH
					// follows a TRY it works the same way,
					// as CATCH and TRY are the same token in the stack).
					expectingOpenBrace = true;
					indentationLevel += pushControlStatement(TokenNamecatch);
					break;
				case TokenNamedo:
					expectingOpenBrace = true;
					indentationLevel += pushControlStatement(token);
					nlicsToken = token;
					break;
				case TokenNamenew:
					break;
				case TokenNameLPAREN:
					// if (previousToken == TokenNamesynchronized) {
					// indentationLevel += pushControlStatement(previousToken);
					// } else {
					// Put a space between the previous and current token if the
					// previous token was not a keyword, open paren, logical
					// compliment (eg: !), semi-colon, open brace, close brace,
					// super, or this.
					if (previousCompilableToken != TokenNameLBRACKET
							&& previousToken != TokenNameIdentifier
							&& previousToken != 0
							&& previousToken != TokenNameNOT
							&& previousToken != TokenNameLPAREN
							&& previousToken != TokenNameTWIDDLE
							&& previousToken != TokenNameSEMICOLON
							&& previousToken != TokenNameLBRACE
							&& previousToken != TokenNameRBRACE
							&& previousToken != TokenNamesuper) {
						// && previousToken != TokenNamethis) {
						if (!options.compactArrays) {
							space();
						}
					}
					// If in a for/if/while statement, increase the parenthesis
					// count
					// for the current openParenthesisCount
					// else increase the count for stand alone parenthesis.
					if (openParenthesisCount > 0)
						openParenthesis[openParenthesisCount - 1]++;
					else
						openParenthesis[0]++;
					pendingSpace = false;
					// recognize array declaration for nice output
					if (previousCompilableToken == TokenNamearray) {
						arrayDeclarationCount++;
						arrayDeclarationParenthesis[arrayDeclarationCount] = openParenthesis[openParenthesisCount - 1];
						if (!options.compactArrays) {
							indentationLevel++;
							pendingNewLines = 1;
						}
					}
					// S }
					break;
				case TokenNameRPAREN:
					// check for closing array declaration
					if (arrayDeclarationCount > 0) {
						if (arrayDeclarationParenthesis[arrayDeclarationCount] == openParenthesis[openParenthesisCount - 1]) {
							if (previousCompilableToken != TokenNameLPAREN) {
								if (!options.compactArrays) {
									newLine(1);
								}
							} else if (previousToken == TokenNameCOMMENT_LINE
									|| previousToken == TokenNameCOMMENT_BLOCK
									|| previousToken == TokenNameCOMMENT_PHPDOC) {
								// prevent to combine comment line and statement line (#1475484)
								if (!options.compactArrays) {
									newLine(1);
								}
							}
							if (!options.compactArrays) {
								indentationLevel--;
								
							}
							currentLineIndentationLevel = indentationLevel;
							pendingNewLines = 0;
							arrayDeclarationCount--;
						}
					}
					// Decrease the parenthesis count
					// if there is no more unclosed parenthesis,
					// a new line and indent may be append (depending on the
					// next
					// token).
					if ((openParenthesisCount > 1)
							&& (openParenthesis[openParenthesisCount - 1] > 0)) {
						openParenthesis[openParenthesisCount - 1]--;
						if (openParenthesis[openParenthesisCount - 1] <= 0) {
							pendingNewlineAfterParen = true;
							inAssignment = false;
							openParenthesisCount--;
						}
					} else {
						openParenthesis[0]--;
					}
					pendingSpace = false;
					break;
				case TokenNameLBRACE:
					if (previousCompilableToken == TokenNameDOLLAR) {
						dollarBraceCount++;
					} else {
						if ((previousCompilableToken == TokenNameRBRACKET)
								|| (previousCompilableToken == TokenNameEQUAL)) {
							// if (previousCompilableToken == TokenNameRBRACKET)
							// {
							inArrayAssignment = true;
							inAssignment = false;
						}
						if (inArrayAssignment) {
							indentationLevel += pushBlock();
						} else {
							// Add new line and increase indentation level after
							// open brace.
							pendingNewLines = 1;
							indentationLevel += pushBlock();
							inAssignment = false;
						}
					}
					break;
				case TokenNameRBRACE:
					if (dollarBraceCount > 0) {
						dollarBraceCount--;
						break;
					}
					if (previousCompilableToken == TokenNameRPAREN) {
						pendingSpace = false;
					}
					if (inArrayAssignment) {
						inArrayAssignment = false;
						pendingNewLines = 1;
						indentationLevel += popInclusiveUntilBlock();
					} else {
						pendingNewLines = 1;
						indentationLevel += popInclusiveUntilBlock();
						if (previousCompilableToken == TokenNameRPAREN) {
							// fix for 1FGDDV6: LFCOM:WIN98 - Weird splitting on
							// message
							// expression
							currentLineBuffer
									.append(options.lineSeparatorSequence);
							increaseLineDelta(options.lineSeparatorSequence.length);
						}
						if (constructionsCount > 0) {
							switch (constructions[constructionsCount - 1]) {
							case TokenNamefor:
							case TokenNameforeach:
								// indentationLevel += popExclusiveUntilBlock();
								// break;
							case TokenNameswitch:
							case TokenNameif:
							case TokenNameelse:
							case TokenNametry:
							case TokenNamecatch:
							case TokenNamefinally:
							case TokenNamewhile:
							case TokenNamedo:
								// case TokenNamesynchronized :
								clearNonBlockIndents = true;
							default:
								break;
							}
						}
					}
					break;
				case TokenNameLBRACKET:
					openBracketCount++;
					pendingSpace = false;
					break;
				case TokenNameRBRACKET:
					openBracketCount -= (openBracketCount > 0) ? 1 : 0;
					// if there is no left bracket to close, the right bracket
					// is
					// ignored.
					pendingSpace = false;
					break;
				case TokenNameCOMMA:
					pendingSpace = false;
					if (arrayDeclarationCount > 0) {
						if (arrayDeclarationParenthesis[arrayDeclarationCount] == openParenthesis[openParenthesisCount - 1]) {
							// there is no left parenthesis to close in current array declaration (#1475484)
							if (!options.compactArrays) {
								pendingNewLines = 1;								
							}
						}
					}
					break;
				case TokenNameDOT:
					if (!options.compactStringConcatenation) {
						space();
					}
					pendingSpace = false;
					break;
				case TokenNameSEMICOLON:
					// Do not generate line terminators in the definition of
					// the for statement.
					// if not in this case, jump a line and reduce indentation
					// after
					// the brace
					// if the block it closes belongs to a conditional statement
					// (if,
					// while, do...).
					if (openParenthesisCount <= 1) {
						pendingNewLines = 1;
						if (expectingOpenBrace) {
							clearNonBlockIndents = true;
							expectingOpenBrace = false;
						}
					}
					inAssignment = false;
					pendingSpace = false;
					break;
				case TokenNamePLUS_PLUS:
				case TokenNameMINUS_MINUS:
					// Do not put a space between a post-increment/decrement
					// and the identifier being modified.
					if (previousToken == TokenNameIdentifier
							|| previousToken == TokenNameRBRACKET
							|| previousToken == TokenNameVariable) {
						pendingSpace = false;
					}
					break;
				case TokenNamePLUS:
					// previously ADDITION
				case TokenNameMINUS:
					// Handle the unary operators plus and minus via a flag
					if (!isLiteralToken(previousToken)
							&& previousToken != TokenNameIdentifier
							&& previousToken != TokenNameRPAREN
							&& previousToken != TokenNameRBRACKET) {
						unarySignModifier = 1;
					}
					break;
				case TokenNameCOLON:
					// In a switch/case statement, add a newline & indent
					// when a colon is encountered.
					if (tokenBeforeColonCount > 0) {
						if (tokenBeforeColon[tokenBeforeColonCount - 1] == TokenNamecase) {
							pendingNewLines = 1;
						}
						tokenBeforeColonCount--;
					}
					break;
				case TokenNameEQUAL:
					inAssignment = true;
					break;
				case Scanner.TokenNameCOMMENT_LINE:
					pendingNewLines = 1;
					//if (inAssignment) {
					//	currentLineIndentationLevel++;
					//}
					break; // a line is always inserted after a one-line
							// comment
				case Scanner.TokenNameCOMMENT_PHPDOC:
				case Scanner.TokenNameCOMMENT_BLOCK:
					currentCommentOffset = getCurrentCommentOffset();
					pendingNewLines = 1;
					break;
				case Scanner.TokenNameWHITESPACE:
					if (!phpTagAndWhitespace) {
						// Count the number of line terminators in the
						// whitespace so
						// line spacing can be preserved near comments.
						char[] source = scanner.source;
						newLinesInWhitespace = 0;
						for (int i = scanner.startPosition, max = scanner.currentPosition; i < max; i++) {
							if (source[i] == '\r') {
								if (i < max - 1) {
									if (source[++i] == '\n') {
										newLinesInWhitespace++;
									} else {
										newLinesInWhitespace++;
									}
								} else {
									newLinesInWhitespace++;
								}
							} else if (source[i] == '\n') {
								newLinesInWhitespace++;
							}
						}
						increaseLineDelta(scanner.startPosition
								- scanner.currentPosition);
						break;
					}
					// case TokenNameHTML :
					// // Add the next token to the formatted source string.
					// // outputCurrentToken(token);
					// int startPosition = scanner.startPosition;
					// flushBuffer();
					// for (int i = startPosition, max =
					// scanner.currentPosition; i <
					// max; i++) {
					// char currentCharacter = scanner.source[i];
					// updateMappedPositions(i);
					// currentLineBuffer.append(currentCharacter);
					// }
					// break;
				default:
					if ((token == TokenNameIdentifier) || isLiteralToken(token)
							|| token == TokenNamesuper) {
						// || token == TokenNamethis) {
						// Do not put a space between a unary operator
						// (eg: ++, --, +, -) and the identifier being modified.
						if (previousToken == TokenNamePLUS_PLUS
								|| previousToken == TokenNameMINUS_MINUS
								|| (previousToken == TokenNameMINUS_GREATER && options.compactDereferencingMode) // ->
								|| (previousToken == TokenNamePLUS && unarySignModifier > 0)
								|| (previousToken == TokenNameMINUS && unarySignModifier > 0)) {
							pendingSpace = false;
						}
						unarySignModifier = 0;
					}
					break;
				}
				// Do not output whitespace tokens.
				if (token != Scanner.TokenNameWHITESPACE || phpTagAndWhitespace) {
					/*
					 * Add pending space to the formatted source string. Do not
					 * output a space under the following circumstances: 1) this
					 * is the first pass 2) previous token is an open paren 3)
					 * previous token is a period 4) previous token is the
					 * logical compliment (eg: !) 5) previous token is the
					 * bitwise compliment (eg: ~) 6) previous token is the open
					 * bracket (eg: [) 7) in an assignment statement, if the
					 * previous token is an open brace or the current token is a
					 * close brace 8) previous token is a single line comment 9)
					 * current token is a '->'
					 */
					if (token == TokenNameMINUS_GREATER
							&& options.compactDereferencingMode)
						pendingSpace = false;

					boolean openAndCloseBrace = previousCompilableToken == TokenNameLBRACE
							&& token == TokenNameRBRACE;
					if (pendingSpace
							&& insertSpaceAfter(previousToken)
							&& !(inAssignment && (previousToken == TokenNameLBRACE || token == TokenNameRBRACE))
							&& previousToken != Scanner.TokenNameCOMMENT_LINE) {
						if ((!(options.compactAssignmentMode && token == TokenNameEQUAL))
								&& !openAndCloseBrace)
							space();
					}
					// Add the next token to the formatted source string.
					outputCurrentToken(token);
					if (token == Scanner.TokenNameCOMMENT_LINE
							&& openParenthesisCount > 1) {
						pendingNewLines = 0;
						currentLineBuffer.append(options.lineSeparatorSequence);
						increaseLineDelta(options.lineSeparatorSequence.length);
					}
					pendingSpace = true;
				}
				// Whitespace tokens do not need to be remembered.
				if (token != Scanner.TokenNameWHITESPACE || phpTagAndWhitespace) {
					previousToken = token;
					if (token != Scanner.TokenNameCOMMENT_BLOCK
							&& token != Scanner.TokenNameCOMMENT_LINE
							&& token != Scanner.TokenNameCOMMENT_PHPDOC) {
						previousCompilableToken = token;
					}
				}
			}
			output(copyRemainingSource());
			flushBuffer();
			// dump the last token of the source in the formatted output.
		} catch (InvalidInputException e) {
			output(copyRemainingSource());
			flushBuffer();
			// dump the last token of the source in the formatted output.
		}
	}

	/**
	 * Formats the char array <code>sourceString</code>, and returns a string
	 * containing the formatted version.
	 * 
	 * @return the formatted ouput.
	 */
	public String formatSourceString(String sourceString) {
		char[] sourceChars = sourceString.toCharArray();
		formattedSource = new StringBuffer(sourceChars.length);
		scanner.setSource(sourceChars);
		format();
		return formattedSource.toString();
	}

	/**
	 * Formats the char array <code>sourceString</code>, and returns a string
	 * containing the formatted version.
	 * 
	 * @param string
	 *            the string to format
	 * @param indentationLevel
	 *            the initial indentation level
	 * @return the formatted ouput.
	 */
	public String format(String string, int indentationLevel) {
		return format(string, indentationLevel, (int[]) null);
	}

	/**
	 * Formats the char array <code>sourceString</code>, and returns a string
	 * containing the formatted version. The positions array is modified to
	 * contain the mapped positions.
	 * 
	 * @param string
	 *            the string to format
	 * @param indentationLevel
	 *            the initial indentation level
	 * @param positions
	 *            the array of positions to map
	 * @return the formatted ouput.
	 */
	public String format(String string, int indentationLevel, int[] positions) {
		return this.format(string, indentationLevel, positions, null);
	}

	public String format(String string, int indentationLevel, int[] positions,
			String lineSeparator) {
		if (lineSeparator != null) {
			this.options.setLineSeparator(lineSeparator);
		}
		if (positions != null) {
			this.setPositionsToMap(positions);
			this.setInitialIndentationLevel(indentationLevel);
			String formattedString = this.formatSourceString(string);
			int[] mappedPositions = this.getMappedPositions();
			System
					.arraycopy(mappedPositions, 0, positions, 0,
							positions.length);
			return formattedString;
		} else {
			this.setInitialIndentationLevel(indentationLevel);
			return this.formatSourceString(string);
		}
	}

	/**
	 * Formats the char array <code>sourceString</code>, and returns a string
	 * containing the formatted version. The initial indentation level is 0.
	 * 
	 * @param string
	 *            the string to format
	 * @return the formatted ouput.
	 */
	public String format(String string) {
		return this.format(string, 0, (int[]) null);
	}

	/**
	 * Formats a given source string, starting indenting it at a particular
	 * depth and using the given options
	 * 
	 * @deprecated backport 1.0 internal functionality
	 */
	public static String format(String sourceString,
			int initialIndentationLevel, ConfigurableOption[] options) {
		CodeFormatter formatter = new CodeFormatter(options);
		formatter.setInitialIndentationLevel(initialIndentationLevel);
		return formatter.formatSourceString(sourceString);
	}

	/**
	 * Returns the number of characters and tab char between the beginning of
	 * the line and the beginning of the comment.
	 */
	private int getCurrentCommentOffset() {
		int linePtr = scanner.linePtr;
		// if there is no beginning of line, return 0.
		if (linePtr < 0)
			return 0;
		int offset = 0;
		int beginningOfLine = scanner.lineEnds[linePtr];
		int currentStartPosition = scanner.startPosition;
		char[] source = scanner.source;
		// find the position of the beginning of the line containing the comment
		while (beginningOfLine > currentStartPosition) {
			if (linePtr > 0) {
				beginningOfLine = scanner.lineEnds[--linePtr];
			} else {
				beginningOfLine = 0;
				break;
			}
		}
		for (int i = currentStartPosition - 1; i >= beginningOfLine; i--) {
			char currentCharacter = source[i];
			switch (currentCharacter) {
			case '\t':
				offset += options.tabSize;
				break;
			case ' ':
				offset++;
				break;
			case '\r':
			case '\n':
				break;
			default:
				return offset;
			}
		}
		return offset;
	}

	/**
	 * Returns an array of descriptions for the configurable options. The
	 * descriptions may be changed and passed back to a different compiler.
	 * 
	 * @deprecated backport 1.0 internal functionality
	 */
	public static ConfigurableOption[] getDefaultOptions(Locale locale) {
		String componentName = CodeFormatter.class.getName();
		FormatterOptions options = new FormatterOptions();
		return new ConfigurableOption[] {
				new ConfigurableOption(componentName, "newline.openingBrace",
						locale, options.newLineBeforeOpeningBraceMode ? 0 : 1),
				//$NON-NLS-1$
				new ConfigurableOption(componentName,
						"newline.controlStatement", locale,
						options.newlineInControlStatementMode ? 0 : 1),
				//$NON-NLS-1$
				new ConfigurableOption(componentName, "newline.clearAll",
						locale, options.clearAllBlankLinesMode ? 0 : 1),
				//$NON-NLS-1$
				// new ConfigurableOption(componentName, "newline.elseIf",
				// locale,
				// options.compactElseIfMode ? 0 : 1), //$NON-NLS-1$
				new ConfigurableOption(componentName, "newline.emptyBlock",
						locale, options.newLineInEmptyBlockMode ? 0 : 1),
				//$NON-NLS-1$
				new ConfigurableOption(componentName, "line.split", locale,
						options.maxLineLength),
				//$NON-NLS-1$
				new ConfigurableOption(componentName,
						"style.compactAssignment", locale,
						options.compactAssignmentMode ? 0 : 1),
				//$NON-NLS-1$
				new ConfigurableOption(componentName, "tabulation.char",
						locale, options.indentWithTab ? 0 : 1),
				//$NON-NLS-1$
				new ConfigurableOption(componentName,
						"tabulation.size", locale, options.tabSize) //$NON-NLS-1$
		};
	}

	/**
	 * Returns the array of mapped positions. Returns null is no positions have
	 * been set.
	 * 
	 * @return int[]
	 * @deprecated There is no need to retrieve the mapped positions anymore.
	 */
	public int[] getMappedPositions() {
		if (null != mappedPositions) {
			for (int i = 0; i < mappedPositions.length; i++) {
				if (mappedPositions[i] >= formattedSource.length()) {
					mappedPositions[i] = formattedSource.length() - 1;
				}
			}
		}
		return mappedPositions;
	}

	/**
	 * Returns the priority of the token given as argument <br>
	 * The most prioritary the token is, the smallest the return value is.
	 * 
	 * @return the priority of <code>token</code>
	 * @param token
	 *            the token of which the priority is requested
	 */
	private static int getTokenPriority(int token) {
		switch (token) {
		case TokenNameextends:
			// case TokenNameimplements :
			// case TokenNamethrows :
			return 10;
		case TokenNameSEMICOLON:
			// ;
			return 20;
		case TokenNameCOMMA:
			// ,
			return 25;
		case TokenNameEQUAL:
			// =
			return 30;
		case TokenNameAND_AND:
			// &&
		case TokenNameOR_OR:
			// ||
			return 40;
		case TokenNameQUESTION:
			// ?
		case TokenNameCOLON:
			// :
			return 50; // it's better cutting on ?: than on ;
		case TokenNameEQUAL_EQUAL:
			// ==
		case TokenNameEQUAL_EQUAL_EQUAL:
			// ===
		case TokenNameNOT_EQUAL:
			// !=
		case TokenNameNOT_EQUAL_EQUAL:
			// !=
			return 60;
		case TokenNameLESS:
			// <
		case TokenNameLESS_EQUAL:
			// <=
		case TokenNameGREATER:
			// >
		case TokenNameGREATER_EQUAL:
			// >=
			// case TokenNameinstanceof : // instanceof
			return 70;
		case TokenNamePLUS:
			// +
		case TokenNameMINUS:
			// -
			return 80;
		case TokenNameMULTIPLY:
			// *
		case TokenNameDIVIDE:
			// /
		case TokenNameREMAINDER:
			// %
			return 90;
		case TokenNameLEFT_SHIFT:
			// <<
		case TokenNameRIGHT_SHIFT:
			// >>
			// case TokenNameUNSIGNED_RIGHT_SHIFT : // >>>
			return 100;
		case TokenNameAND:
			// &
		case TokenNameOR:
			// |
		case TokenNameXOR:
			// ^
			return 110;
		case TokenNameMULTIPLY_EQUAL:
			// *=
		case TokenNameDIVIDE_EQUAL:
			// /=
		case TokenNameREMAINDER_EQUAL:
			// %=
		case TokenNamePLUS_EQUAL:
			// +=
		case TokenNameMINUS_EQUAL:
			// -=
		case TokenNameLEFT_SHIFT_EQUAL:
			// <<=
		case TokenNameRIGHT_SHIFT_EQUAL:
			// >>=
			// case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>=
		case TokenNameAND_EQUAL:
			// &=
		case TokenNameXOR_EQUAL:
			// ^=
		case TokenNameOR_EQUAL:
			// .=
		case TokenNameDOT_EQUAL:
			// |=
			return 120;
		case TokenNameDOT:
			// .
			return 130;
		default:
			return Integer.MAX_VALUE;
		}
	}

	/**
	 * Handles the exception raised when an invalid token is encountered.
	 * Returns true if the exception has been handled, false otherwise.
	 */
	private boolean handleInvalidToken(Exception e) {
		if (e.getMessage().equals(Scanner.INVALID_CHARACTER_CONSTANT)
				|| e.getMessage().equals(Scanner.INVALID_CHAR_IN_STRING)
				|| e.getMessage().equals(Scanner.INVALID_ESCAPE)) {
			return true;
		}
		return false;
	}

	private final void increaseGlobalDelta(int offset) {
		globalDelta += offset;
	}

	private final void increaseLineDelta(int offset) {
		lineDelta += offset;
	}

	private final void increaseSplitDelta(int offset) {
		splitDelta += offset;
	}

	/**
	 * Returns true if a space has to be inserted after <code>operator</code>
	 * false otherwise.
	 */
	private boolean insertSpaceAfter(int token) {
		switch (token) {
		case TokenNameLPAREN:
		case TokenNameNOT:
		case TokenNameTWIDDLE:
		case 0:
			// no token
		case TokenNameWHITESPACE:
		case TokenNameLBRACKET:
		case TokenNameDOLLAR:
		case Scanner.TokenNameCOMMENT_LINE:
			return false;
		case TokenNameDOT:
			return !options.compactStringConcatenation;
		default:
			return true;
		}
	}

	/**
	 * Returns true if a space has to be inserted before <code>operator</code>
	 * false otherwise. <br>
	 * Cannot be static as it uses the code formatter options (to know if the
	 * compact assignment mode is on).
	 */
	private boolean insertSpaceBefore(int token) {
		switch (token) {
		case TokenNameEQUAL:
			return (!options.compactAssignmentMode);
		default:
			return false;
		}
	}

	private static boolean isComment(int token) {
		boolean result = token == Scanner.TokenNameCOMMENT_BLOCK
				|| token == Scanner.TokenNameCOMMENT_LINE
				|| token == Scanner.TokenNameCOMMENT_PHPDOC;
		return result;
	}

	private static boolean isLiteralToken(int token) {
		boolean result = token == TokenNameIntegerLiteral
		// || token == TokenNameLongLiteral
				// || token == TokenNameFloatingPointLiteral
				|| token == TokenNameDoubleLiteral
				// || token == TokenNameCharacterLiteral
				|| token == TokenNameStringDoubleQuote;
		return result;
	}

	/**
	 * If the length of <code>oneLineBuffer</code> exceeds
	 * <code>maxLineLength</code>, it is split and the result is dumped in
	 * <code>formattedSource</code>
	 * 
	 * @param newLineCount
	 *            the number of new lines to append
	 */
	private void newLine(int newLineCount) {
		// format current line
		splitDelta = 0;
		beginningOfLineIndex = formattedSource.length();
		String currentLine = currentLineBuffer.toString();
		if (containsOpenCloseBraces) {
			containsOpenCloseBraces = false;
			outputLine(currentLine, false, indentationLevelForOpenCloseBraces,
					0, -1, null, 0);
			indentationLevelForOpenCloseBraces = currentLineIndentationLevel;
		} else {
			outputLine(currentLine, false, currentLineIndentationLevel, 0, -1,
					null, 0);
		}
		// dump line break(s)
		for (int i = 0; i < newLineCount; i++) {
			formattedSource.append(options.lineSeparatorSequence);
			increaseSplitDelta(options.lineSeparatorSequence.length);
		}
		// reset formatter for next line
		int currentLength = currentLine.length();
		currentLineBuffer = new StringBuffer(
				currentLength > maxLineSize ? maxLineSize = currentLength
						: maxLineSize);
		increaseGlobalDelta(splitDelta);
		increaseGlobalDelta(lineDelta);
		lineDelta = 0;
		currentLineIndentationLevel = initialIndentationLevel;
	}

	private String operatorString(int operator) {
		switch (operator) {
		case TokenNameextends:
			return "extends"; //$NON-NLS-1$
			// case TokenNameimplements :
			// return "implements"; //$NON-NLS-1$
			//
			// case TokenNamethrows :
			// return "throws"; //$NON-NLS-1$
		case TokenNameSEMICOLON:
			// ;
			return ";"; //$NON-NLS-1$
		case TokenNameCOMMA:
			// ,
			return ","; //$NON-NLS-1$
		case TokenNameEQUAL:
			// =
			return "="; //$NON-NLS-1$
		case TokenNameAND_AND:
			// && (15.22)
			return "&&"; //$NON-NLS-1$
		case TokenNameOR_OR:
			// || (15.23)
			return "||"; //$NON-NLS-1$
		case TokenNameQUESTION:
			// ? (15.24)
			return "?"; //$NON-NLS-1$
		case TokenNameCOLON:
			// : (15.24)
			return ":"; //$NON-NLS-1$
		case TokenNamePAAMAYIM_NEKUDOTAYIM:
			// : (15.24)
			return "::"; //$NON-NLS-1$
		case TokenNameEQUAL_EQUAL:
			// == (15.20, 15.20.1, 15.20.2, 15.20.3)
			return "=="; //$NON-NLS-1$
		case TokenNameEQUAL_EQUAL_EQUAL:
			// == (15.20, 15.20.1, 15.20.2, 15.20.3)
			return "==="; //$NON-NLS-1$
		case TokenNameEQUAL_GREATER:
			// -= (15.25.2)
			return "=>"; //$NON-NLS-1$
		case TokenNameNOT_EQUAL:
			// != (15.20, 15.20.1, 15.20.2, 15.20.3)
			return "!="; //$NON-NLS-1$
		case TokenNameNOT_EQUAL_EQUAL:
			// != (15.20, 15.20.1, 15.20.2, 15.20.3)
			return "!=="; //$NON-NLS-1$
		case TokenNameLESS:
			// < (15.19.1)
			return "<"; //$NON-NLS-1$
		case TokenNameLESS_EQUAL:
			// <= (15.19.1)
			return "<="; //$NON-NLS-1$
		case TokenNameGREATER:
			// > (15.19.1)
			return ">"; //$NON-NLS-1$
		case TokenNameGREATER_EQUAL:
			// >= (15.19.1)
			return ">="; //$NON-NLS-1$
			// case TokenNameinstanceof : // instanceof
			// return "instanceof"; //$NON-NLS-1$
		case TokenNamePLUS:
			// + (15.17, 15.17.2)
			return "+"; //$NON-NLS-1$
		case TokenNameMINUS:
			// - (15.17.2)
			return "-"; //$NON-NLS-1$
		case TokenNameMULTIPLY:
			// * (15.16.1)
			return "*"; //$NON-NLS-1$
		case TokenNameDIVIDE:
			// / (15.16.2)
			return "/"; //$NON-NLS-1$
		case TokenNameREMAINDER:
			// % (15.16.3)
			return "%"; //$NON-NLS-1$
		case TokenNameLEFT_SHIFT:
			// << (15.18)
			return "<<"; //$NON-NLS-1$
		case TokenNameRIGHT_SHIFT:
			// >> (15.18)
			return ">>"; //$NON-NLS-1$
			// case TokenNameUNSIGNED_RIGHT_SHIFT : // >>> (15.18)
			// return ">>>"; //$NON-NLS-1$
		case TokenNameAND:
			// & (15.21, 15.21.1, 15.21.2)
			return "&"; //$NON-NLS-1$
		case TokenNameOR:
			// | (15.21, 15.21.1, 15.21.2)
			return "|"; //$NON-NLS-1$
		case TokenNameXOR:
			// ^ (15.21, 15.21.1, 15.21.2)
			return "^"; //$NON-NLS-1$
		case TokenNameMULTIPLY_EQUAL:
			// *= (15.25.2)
			return "*="; //$NON-NLS-1$
		case TokenNameDIVIDE_EQUAL:
			// /= (15.25.2)
			return "/="; //$NON-NLS-1$
		case TokenNameREMAINDER_EQUAL:
			// %= (15.25.2)
			return "%="; //$NON-NLS-1$
		case TokenNamePLUS_EQUAL:
			// += (15.25.2)
			return "+="; //$NON-NLS-1$
		case TokenNameMINUS_EQUAL:
			// -= (15.25.2)
			return "-="; //$NON-NLS-1$
		case TokenNameMINUS_GREATER:
			// -= (15.25.2)
			return "->"; //$NON-NLS-1$
		case TokenNameLEFT_SHIFT_EQUAL:
			// <<= (15.25.2)
			return "<<="; //$NON-NLS-1$
		case TokenNameRIGHT_SHIFT_EQUAL:
			// >>= (15.25.2)
			return ">>="; //$NON-NLS-1$
			// case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>= (15.25.2)
			// return ">>>="; //$NON-NLS-1$
		case TokenNameAND_EQUAL:
			// &= (15.25.2)
			return "&="; //$NON-NLS-1$
		case TokenNameXOR_EQUAL:
			// ^= (15.25.2)
			return "^="; //$NON-NLS-1$
		case TokenNameOR_EQUAL:
			// |= (15.25.2)
			return "|="; //$NON-NLS-1$
		case TokenNameDOT_EQUAL:
			// .=
			return ".="; //$NON-NLS-1$
		case TokenNameDOT:
			// .
			return "."; //$NON-NLS-1$
		default:
			return ""; //$NON-NLS-1$
		}
	}

	/**
	 * Appends <code>stringToOutput</code> to the formatted output. <br>
	 * If it contains \n, append a LINE_SEPARATOR and indent after it.
	 */
	private void output(String stringToOutput) {
		char currentCharacter;
		for (int i = 0, max = stringToOutput.length(); i < max; i++) {
			currentCharacter = stringToOutput.charAt(i);
			if (currentCharacter != '\t') {
				currentLineBuffer.append(currentCharacter);
			}
		}
	}

	private void outputCurrentTokenWithoutIndent(int token, int newLineCount) {
		newLine(newLineCount);
		formattedSource.append(scanner.source, scanner.startPosition,
				scanner.currentPosition - scanner.startPosition);
	}

	/**
	 * Appends <code>token</code> to the formatted output. <br>
	 * If it contains <code>\n</code>, append a LINE_SEPARATOR and indent
	 * after it.
	 */
	private void outputCurrentToken(int token) {
		char[] source = scanner.source;
		int startPosition = scanner.startPosition;
		switch (token) {
		case Scanner.TokenNameCOMMENT_PHPDOC:
		case Scanner.TokenNameCOMMENT_BLOCK:
		case Scanner.TokenNameCOMMENT_LINE:
			boolean endOfLine = false;
			int currentCommentOffset = getCurrentCommentOffset();
			int beginningOfLineSpaces = 0;
			endOfLine = false;
			currentCommentOffset = getCurrentCommentOffset();
			beginningOfLineSpaces = 0;
			boolean pendingCarriageReturn = false;
			for (int i = startPosition, max = scanner.currentPosition; i < max; i++) {
				char currentCharacter = source[i];
				updateMappedPositions(i);
				switch (currentCharacter) {
				case '\r':
					pendingCarriageReturn = true;
					endOfLine = true;
					break;
				case '\n':
					if (pendingCarriageReturn) {
						increaseGlobalDelta(options.lineSeparatorSequence.length - 2);
					} else {
						increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
					}
					pendingCarriageReturn = false;
					currentLineBuffer.append(options.lineSeparatorSequence);
					beginningOfLineSpaces = 0;
					endOfLine = true;
					break;
				case '\t':
					if (pendingCarriageReturn) {
						pendingCarriageReturn = false;
						increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
						currentLineBuffer.append(options.lineSeparatorSequence);
						beginningOfLineSpaces = 0;
						endOfLine = true;
					}
					if (endOfLine) {
						// we remove a maximum of currentCommentOffset
						// characters (tabs
						// are converted to space numbers).
						beginningOfLineSpaces += options.tabSize;
						if (beginningOfLineSpaces > currentCommentOffset) {
							currentLineBuffer.append(currentCharacter);
						} else {
							increaseGlobalDelta(-1);
						}
					} else {
						currentLineBuffer.append(currentCharacter);
					}
					break;
				case ' ':
					if (pendingCarriageReturn) {
						pendingCarriageReturn = false;
						increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
						currentLineBuffer.append(options.lineSeparatorSequence);
						beginningOfLineSpaces = 0;
						endOfLine = true;
					}
					if (endOfLine) {
						// we remove a maximum of currentCommentOffset
						// characters (tabs
						// are converted to space numbers).
						beginningOfLineSpaces++;
						if (beginningOfLineSpaces > currentCommentOffset) {
							currentLineBuffer.append(currentCharacter);
						} else {
							increaseGlobalDelta(-1);
						}
					} else {
						currentLineBuffer.append(currentCharacter);
					}
					break;
				default:
					if (pendingCarriageReturn) {
						pendingCarriageReturn = false;
						increaseGlobalDelta(options.lineSeparatorSequence.length - 1);
						currentLineBuffer.append(options.lineSeparatorSequence);
						beginningOfLineSpaces = 0;
						endOfLine = true;
					} else {
						beginningOfLineSpaces = 0;
						currentLineBuffer.append(currentCharacter);
						endOfLine = false;
					}
				}
			}
			updateMappedPositions(scanner.currentPosition - 1);
			multipleLineCommentCounter++;
			break;
		default:
			for (int i = startPosition, max = scanner.currentPosition; i < max; i++) {
				char currentCharacter = source[i];
				updateMappedPositions(i);
				currentLineBuffer.append(currentCharacter);
			}
		}
	}

	/**
	 * Outputs <code>currentString</code>:<br>
	 * <ul>
	 * <li>If its length is < maxLineLength, output
	 * <li>Otherwise it is split.
	 * </ul>
	 * 
	 * @param currentString
	 *            string to output
	 * @param preIndented
	 *            whether the string to output was pre-indented
	 * @param depth
	 *            number of indentation to put in front of
	 *            <code>currentString</code>
	 * @param operator
	 *            value of the operator belonging to <code>currentString</code>.
	 */
	private void outputLine(String currentString, boolean preIndented,
			int depth, int operator, int substringIndex,
			int[] startSubstringIndexes, int offsetInGlobalLine) {
		boolean emptyFirstSubString = false;
		String operatorString = operatorString(operator);
		boolean placeOperatorBehind = !breakLineBeforeOperator(operator);
		boolean placeOperatorAhead = !placeOperatorBehind;
		// dump prefix operator?
		if (placeOperatorAhead) {
			if (!preIndented) {
				dumpTab(depth);
				preIndented = true;
			}
			if (operator != 0) {
				if (insertSpaceBefore(operator)) {
					formattedSource.append(' ');
					increaseSplitDelta(1);
				}
				formattedSource.append(operatorString);
				increaseSplitDelta(operatorString.length());
				if (insertSpaceAfter(operator)
						&& operator != TokenNameimplements
						&& operator != TokenNameextends) {
					// && operator != TokenNamethrows) {
					formattedSource.append(' ');
					increaseSplitDelta(1);
				}
			}
		}
		SplitLine splitLine = null;
		if (options.maxLineLength == 0
				|| getLength(currentString, depth) < options.maxLineLength
				|| (splitLine = split(currentString, offsetInGlobalLine)) == null) {
			// depending on the type of operator, outputs new line before of
			// after
			// dumping it
			// indent before postfix operator
			// indent also when the line cannot be split
			if (operator == TokenNameextends || operator == TokenNameimplements) {
				// || operator == TokenNamethrows) {
				formattedSource.append(' ');
				increaseSplitDelta(1);
			}
			if (placeOperatorBehind) {
				if (!preIndented) {
					dumpTab(depth);
				}
			}
			int max = currentString.length();
			if (multipleLineCommentCounter != 0) {
				try {
					BufferedReader reader = new BufferedReader(
							new StringReader(currentString));
					String line = reader.readLine();
					while (line != null) {
						updateMappedPositionsWhileSplitting(
								beginningOfLineIndex, beginningOfLineIndex
										+ line.length()
										+ options.lineSeparatorSequence.length);
						formattedSource.append(line);
						beginningOfLineIndex = beginningOfLineIndex
								+ line.length();
						if ((line = reader.readLine()) != null) {
							formattedSource
									.append(options.lineSeparatorSequence);
							beginningOfLineIndex += options.lineSeparatorSequence.length;
							dumpTab(currentLineIndentationLevel);
						}
					}
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else {
				updateMappedPositionsWhileSplitting(beginningOfLineIndex,
						beginningOfLineIndex + max);
				for (int i = 0; i < max; i++) {
					char currentChar = currentString.charAt(i);
					switch (currentChar) {
					case '\r':
						break;
					case '\n':
						if (i != max - 1) {
							// fix for 1FFYL5C: LFCOM:ALL - Incorrect
							// indentation when
							// split with a comment inside a condition
							// a substring cannot end with a
							// lineSeparatorSequence,
							// except if it has been added by format() after a
							// one-line
							// comment
							formattedSource
									.append(options.lineSeparatorSequence);
							// 1FGDDV6: LFCOM:WIN98 - Weird splitting on message
							// expression
							dumpTab(depth - 1);
						}
						break;
					default:
						formattedSource.append(currentChar);
					}
				}
			}
			// update positions inside the mappedPositions table
			if (substringIndex != -1) {
				if (multipleLineCommentCounter == 0) {
					int startPosition = beginningOfLineIndex
							+ startSubstringIndexes[substringIndex];
					updateMappedPositionsWhileSplitting(startPosition,
							startPosition + max);
				}
				// compute the splitDelta resulting with the operator and blank
				// removal
				if (substringIndex + 1 != startSubstringIndexes.length) {
					increaseSplitDelta(startSubstringIndexes[substringIndex]
							+ max - startSubstringIndexes[substringIndex + 1]);
				}
			}
			// dump postfix operator?
			if (placeOperatorBehind) {
				if (insertSpaceBefore(operator)) {
					formattedSource.append(' ');
					if (operator != 0) {
						increaseSplitDelta(1);
					}
				}
				formattedSource.append(operatorString);
				if (operator != 0) {
					increaseSplitDelta(operatorString.length());
				}
			}
			return;
		}
		// fix for 1FG0BA3: LFCOM:WIN98 - Weird splitting on interfaces
		// extends has to stand alone on a line when currentString has been
		// split.
		if (options.maxLineLength != 0 && splitLine != null
				&& (operator == TokenNameextends)) {
			// || operator == TokenNameimplements
			// || operator == TokenNamethrows)) {
			formattedSource.append(options.lineSeparatorSequence);
			increaseSplitDelta(options.lineSeparatorSequence.length);
			dumpTab(depth + 1);
		} else {
			if (operator == TokenNameextends) {
				// || operator == TokenNameimplements
				// || operator == TokenNamethrows) {
				formattedSource.append(' ');
				increaseSplitDelta(1);
			}
		}
		// perform actual splitting
		String result[] = splitLine.substrings;
		int[] splitOperators = splitLine.operators;
		if (result[0].length() == 0) {
			// when the substring 0 is null, the substring 1 is correctly
			// indented.
			depth--;
			emptyFirstSubString = true;
		}
		// the operator going in front of the result[0] string is the operator
		// parameter
		for (int i = 0, max = result.length; i < max; i++) {
			// the new depth is the current one if this is the first substring,
			// the current one + 1 otherwise.
			// if the substring is a comment, use the current indentation Level
			// instead of the depth
			// (-1 because the ouputline increases depth).
			// (fix for 1FFC72R: LFCOM:ALL - Incorrect line split in presence of
			// line
			// comments)
			String currentResult = result[i];
			if (currentResult.length() != 0 || splitOperators[i] != 0) {
				int newDepth = (currentResult.startsWith("/*") //$NON-NLS-1$
				|| currentResult.startsWith("//")) //$NON-NLS-1$
				? indentationLevel - 1
						: depth;
				outputLine(currentResult, i == 0
						|| (i == 1 && emptyFirstSubString) ? preIndented
						: false, i == 0 ? newDepth : newDepth + 1,
						splitOperators[i], i, splitLine.startSubstringsIndexes,
						currentString.indexOf(currentResult));
				if (i != max - 1) {
					formattedSource.append(options.lineSeparatorSequence);
					increaseSplitDelta(options.lineSeparatorSequence.length);
				}
			}
		}
		if (result.length == splitOperators.length - 1) {
			int lastOperator = splitOperators[result.length];
			String lastOperatorString = operatorString(lastOperator);
			formattedSource.append(options.lineSeparatorSequence);
			increaseSplitDelta(options.lineSeparatorSequence.length);
			if (breakLineBeforeOperator(lastOperator)) {
				dumpTab(depth + 1);
				if (lastOperator != 0) {
					if (insertSpaceBefore(lastOperator)) {
						formattedSource.append(' ');
						increaseSplitDelta(1);
					}
					formattedSource.append(lastOperatorString);
					increaseSplitDelta(lastOperatorString.length());
					if (insertSpaceAfter(lastOperator)
							&& lastOperator != TokenNameimplements
							&& lastOperator != TokenNameextends) {
						// && lastOperator != TokenNamethrows) {
						formattedSource.append(' ');
						increaseSplitDelta(1);
					}
				}
			}
		}
		if (placeOperatorBehind) {
			if (insertSpaceBefore(operator)) {
				formattedSource.append(' ');
				increaseSplitDelta(1);
			}
			formattedSource.append(operatorString);
			// increaseSplitDelta(operatorString.length());
		}
	}

	/**
	 * Pops the top statement of the stack if it is <code>token</code>
	 */
	private int pop(int token) {
		int delta = 0;
		if ((constructionsCount > 0)
				&& (constructions[constructionsCount - 1] == token)) {
			delta--;
			constructionsCount--;
		}
		return delta;
	}

	/**
	 * Pops the top statement of the stack if it is a <code>BLOCK</code> or a
	 * <code>NONINDENT_BLOCK</code>.
	 */
	private int popBlock() {
		int delta = 0;
		if ((constructionsCount > 0)
				&& ((constructions[constructionsCount - 1] == BLOCK) || (constructions[constructionsCount - 1] == NONINDENT_BLOCK))) {
			if (constructions[constructionsCount - 1] == BLOCK)
				delta--;
			constructionsCount--;
		}
		return delta;
	}

	/**
	 * Pops elements until the stack is empty or the top element is
	 * <code>token</code>.<br>
	 * Does not remove <code>token</code> from the stack.
	 * 
	 * @param token
	 *            the token to be left as the top of the stack
	 */
	private int popExclusiveUntil(int token) {
		int delta = 0;
		int startCount = constructionsCount;
		for (int i = startCount - 1; i >= 0 && constructions[i] != token; i--) {
			if (constructions[i] != NONINDENT_BLOCK)
				delta--;
			constructionsCount--;
		}
		return delta;
	}

	/**
	 * Pops elements until the stack is empty or the top element is a
	 * <code>BLOCK</code> or a <code>NONINDENT_BLOCK</code>.<br>
	 * Does not remove it from the stack.
	 */
	private int popExclusiveUntilBlock() {
		int startCount = constructionsCount;
		int delta = 0;
		for (int i = startCount - 1; i >= 0 && constructions[i] != BLOCK
				&& constructions[i] != NONINDENT_BLOCK; i--) {
			constructionsCount--;
			delta--;
		}
		return delta;
	}

	/**
	 * Pops elements until the stack is empty or the top element is a
	 * <code>BLOCK</code>, a <code>NONINDENT_BLOCK</code> or a
	 * <code>CASE</code>.<br>
	 * Does not remove it from the stack.
	 */
	private int popExclusiveUntilBlockOrCase() {
		int startCount = constructionsCount;
		int delta = 0;
		for (int i = startCount - 1; i >= 0 && constructions[i] != BLOCK
				&& constructions[i] != NONINDENT_BLOCK
				&& constructions[i] != TokenNamecase; i--) {
			constructionsCount--;
			delta--;
		}
		return delta;
	}

	/**
	 * Pops elements until the stack is empty or the top element is
	 * <code>token</code>.<br>
	 * Removes <code>token</code> from the stack too.
	 * 
	 * @param token
	 *            the token to remove from the stack
	 */
	private int popInclusiveUntil(int token) {
		int startCount = constructionsCount;
		int delta = 0;
		for (int i = startCount - 1; i >= 0 && constructions[i] != token; i--) {
			if (constructions[i] != NONINDENT_BLOCK)
				delta--;
			constructionsCount--;
		}
		if (constructionsCount > 0) {
			if (constructions[constructionsCount - 1] != NONINDENT_BLOCK)
				delta--;
			constructionsCount--;
		}
		return delta;
	}

	/**
	 * Pops elements until the stack is empty or the top element is a
	 * <code>BLOCK</code> or a <code>NONINDENT_BLOCK</code>.<br>
	 * Does not remove it from the stack.
	 */
	private int popInclusiveUntilBlock() {
		int startCount = constructionsCount;
		int delta = 0;
		for (int i = startCount - 1; i >= 0
				&& (constructions[i] != BLOCK && constructions[i] != NONINDENT_BLOCK); i--) {
			delta--;
			constructionsCount--;
		}
		if (constructionsCount > 0) {
			if (constructions[constructionsCount - 1] == BLOCK)
				delta--;
			constructionsCount--;
		}
		return delta;
	}

	/**
	 * Pushes a block in the stack. <br>
	 * Pushes a <code>BLOCK</code> if the stack is empty or if the top element
	 * is a <code>BLOCK</code>, pushes <code>NONINDENT_BLOCK</code>
	 * otherwise. Creates a new bigger array if the current one is full.
	 */
	private int pushBlock() {
		int delta = 0;
		if (constructionsCount == constructions.length)
			System.arraycopy(constructions, 0,
					(constructions = new int[constructionsCount * 2]), 0,
					constructionsCount);
		if ((constructionsCount == 0)
				|| (constructions[constructionsCount - 1] == BLOCK)
				|| (constructions[constructionsCount - 1] == NONINDENT_BLOCK)
				|| (constructions[constructionsCount - 1] == TokenNamecase)) {
			delta++;
			constructions[constructionsCount++] = BLOCK;
		} else {
			constructions[constructionsCount++] = NONINDENT_BLOCK;
		}
		return delta;
	}

	/**
	 * Pushes <code>token</code>.<br>
	 * Creates a new bigger array if the current one is full.
	 */
	private int pushControlStatement(int token) {
		if (constructionsCount == constructions.length)
			System.arraycopy(constructions, 0,
					(constructions = new int[constructionsCount * 2]), 0,
					constructionsCount);
		constructions[constructionsCount++] = token;
		return 1;
	}

	private static boolean separateFirstArgumentOn(int currentToken) {
		// return (currentToken == TokenNameCOMMA || currentToken ==
		// TokenNameSEMICOLON);
		return currentToken != TokenNameif && currentToken != TokenNameLPAREN
				&& currentToken != TokenNameNOT
				&& currentToken != TokenNamewhile
				&& currentToken != TokenNamefor
				&& currentToken != TokenNameforeach
				&& currentToken != TokenNameswitch;
	}

	/**
	 * Set the positions to map. The mapped positions should be retrieved using
	 * the getMappedPositions() method.
	 * 
	 * @param positions
	 *            int[]
	 * @deprecated Set the positions to map using the format(String, int, int[])
	 *             method.
	 * 
	 * @see #getMappedPositions()
	 */
	public void setPositionsToMap(int[] positions) {
		positionsToMap = positions;
		lineDelta = 0;
		globalDelta = 0;
		mappedPositions = new int[positions.length];
	}

	/**
	 * Appends a space character to the current line buffer.
	 */
	private void space() {
		currentLineBuffer.append(' ');
		increaseLineDelta(1);
	}

	/**
	 * Splits <code>stringToSplit</code> on the top level token <br>
	 * If there are several identical token at the same level, the string is cut
	 * into many pieces.
	 * 
	 * @return an object containing the operator and all the substrings or null
	 *         if the string cannot be split
	 */
	public SplitLine split(String stringToSplit) {
		return split(stringToSplit, 0);
	}

	/**
	 * Splits <code>stringToSplit</code> on the top level token <br>
	 * If there are several identical token at the same level, the string is cut
	 * into many pieces.
	 * 
	 * @return an object containing the operator and all the substrings or null
	 *         if the string cannot be split
	 */
	public SplitLine split(String stringToSplit, int offsetInGlobalLine) {
		/*
		 * See http://dev.eclipse.org/bugs/show_bug.cgi?id=12540 and
		 * http://dev.eclipse.org/bugs/show_bug.cgi?id=14387
		 */
		if (stringToSplit.indexOf("//$NON-NLS") != -1) { //$NON-NLS-1$
			return null;
		}
		// split doesn't work correct for PHP
		return null;
		// local variables
		// int currentToken = 0;
		// int splitTokenType = 0;
		// int splitTokenDepth = Integer.MAX_VALUE;
		// int splitTokenPriority = Integer.MAX_VALUE;
		// int[] substringsStartPositions = new int[10];
		// // contains the start position of substrings
		// int[] substringsEndPositions = new int[10];
		// // contains the start position of substrings
		// int substringsCount = 1; // index in the substringsStartPosition
		// array
		// int[] splitOperators = new int[10];
		// // contains the start position of substrings
		// int splitOperatorsCount = 0; // index in the substringsStartPosition
		// array
		// int[] openParenthesisPosition = new int[10];
		// int openParenthesisPositionCount = 0;
		// int position = 0;
		// int lastOpenParenthesisPosition = -1;
		// // used to remember the position of the 1st open parenthesis
		// // needed for a pattern like: A.B(C); we want formatted like A.B(
		// split C);
		// // setup the scanner with a new source
		// int lastCommentStartPosition = -1;
		// // to remember the start position of the last comment
		// int firstTokenOnLine = -1;
		// // to remember the first token of the line
		// int previousToken = -1;
		// // to remember the previous token.
		// splitScanner.setSource(stringToSplit.toCharArray());
		// try {
		// // start the loop
		// while (true) {
		// // takes the next token
		// try {
		// if (currentToken != Scanner.TokenNameWHITESPACE)
		// previousToken = currentToken;
		// currentToken = splitScanner.getNextToken();
		// if (Scanner.DEBUG) {
		// int currentEndPosition = splitScanner.getCurrentTokenEndPosition();
		// int currentStartPosition = splitScanner
		// .getCurrentTokenStartPosition();
		// System.out.print(currentStartPosition + "," + currentEndPosition
		// + ": ");
		// System.out.println(scanner.toStringAction(currentToken));
		// }
		// } catch (InvalidInputException e) {
		// if (!handleInvalidToken(e))
		// throw e;
		// currentToken = 0;
		// // this value is not modify when an exception is raised.
		// }
		// if (currentToken == TokenNameEOF)
		// break;
		// if (firstTokenOnLine == -1) {
		// firstTokenOnLine = currentToken;
		// }
		// switch (currentToken) {
		// case TokenNameRBRACE :
		// case TokenNameRPAREN :
		// if (openParenthesisPositionCount > 0) {
		// if (openParenthesisPositionCount == 1
		// && lastOpenParenthesisPosition < openParenthesisPosition[0]) {
		// lastOpenParenthesisPosition = openParenthesisPosition[0];
		// } else if ((splitTokenDepth == Integer.MAX_VALUE)
		// || (splitTokenDepth > openParenthesisPositionCount &&
		// openParenthesisPositionCount == 1)) {
		// splitTokenType = 0;
		// splitTokenDepth = openParenthesisPositionCount;
		// splitTokenPriority = Integer.MAX_VALUE;
		// substringsStartPositions[0] = 0;
		// // better token means the whole line until now is the first
		// // substring
		// substringsCount = 1; // resets the count of substrings
		// substringsEndPositions[0] = openParenthesisPosition[0];
		// // substring ends on operator start
		// position = openParenthesisPosition[0];
		// // the string mustn't be cut before the closing parenthesis but
		// // after the opening one.
		// splitOperatorsCount = 1; // resets the count of split operators
		// splitOperators[0] = 0;
		// }
		// openParenthesisPositionCount--;
		// }
		// break;
		// case TokenNameLBRACE :
		// case TokenNameLPAREN :
		// if (openParenthesisPositionCount == openParenthesisPosition.length) {
		// System
		// .arraycopy(
		// openParenthesisPosition,
		// 0,
		// (openParenthesisPosition = new int[openParenthesisPositionCount *
		// 2]),
		// 0, openParenthesisPositionCount);
		// }
		// openParenthesisPosition[openParenthesisPositionCount++] =
		// splitScanner.currentPosition;
		// if (currentToken == TokenNameLPAREN
		// && previousToken == TokenNameRPAREN) {
		// openParenthesisPosition[openParenthesisPositionCount - 1] =
		// splitScanner.startPosition;
		// }
		// break;
		// case TokenNameSEMICOLON :
		// // ;
		// case TokenNameCOMMA :
		// // ,
		// case TokenNameEQUAL :
		// // =
		// if (openParenthesisPositionCount < splitTokenDepth
		// || (openParenthesisPositionCount == splitTokenDepth &&
		// splitTokenPriority > getTokenPriority(currentToken))) {
		// // the current token is better than the one we currently have
		// // (in level or in priority if same level)
		// // reset the substringsCount
		// splitTokenDepth = openParenthesisPositionCount;
		// splitTokenType = currentToken;
		// splitTokenPriority = getTokenPriority(currentToken);
		// substringsStartPositions[0] = 0;
		// // better token means the whole line until now is the first
		// // substring
		// if (separateFirstArgumentOn(firstTokenOnLine)
		// && openParenthesisPositionCount > 0) {
		// substringsCount = 2; // resets the count of substrings
		// substringsEndPositions[0] = openParenthesisPosition[splitTokenDepth -
		// 1];
		// substringsStartPositions[1] = openParenthesisPosition[splitTokenDepth
		// - 1];
		// substringsEndPositions[1] = splitScanner.startPosition;
		// splitOperatorsCount = 2; // resets the count of split operators
		// splitOperators[0] = 0;
		// splitOperators[1] = currentToken;
		// position = splitScanner.currentPosition;
		// // next substring will start from operator end
		// } else {
		// substringsCount = 1; // resets the count of substrings
		// substringsEndPositions[0] = splitScanner.startPosition;
		// // substring ends on operator start
		// position = splitScanner.currentPosition;
		// // next substring will start from operator end
		// splitOperatorsCount = 1; // resets the count of split operators
		// splitOperators[0] = currentToken;
		// }
		// } else {
		// if ((openParenthesisPositionCount == splitTokenDepth &&
		// splitTokenPriority == getTokenPriority(currentToken))
		// && splitTokenType != TokenNameEQUAL
		// && currentToken != TokenNameEQUAL) {
		// // fix for 1FG0BCN: LFCOM:WIN98 - Missing one indentation after
		// // split
		// // take only the 1st = into account.
		// // if another token with the same priority is found,
		// // push the start position of the substring and
		// // push the token into the stack.
		// // create a new array object if the current one is full.
		// if (substringsCount == substringsStartPositions.length) {
		// System
		// .arraycopy(
		// substringsStartPositions,
		// 0,
		// (substringsStartPositions = new int[substringsCount * 2]),
		// 0, substringsCount);
		// System.arraycopy(substringsEndPositions, 0,
		// (substringsEndPositions = new int[substringsCount * 2]),
		// 0, substringsCount);
		// }
		// if (splitOperatorsCount == splitOperators.length) {
		// System.arraycopy(splitOperators, 0,
		// (splitOperators = new int[splitOperatorsCount * 2]), 0,
		// splitOperatorsCount);
		// }
		// substringsStartPositions[substringsCount] = position;
		// substringsEndPositions[substringsCount++] =
		// splitScanner.startPosition;
		// // substring ends on operator start
		// position = splitScanner.currentPosition;
		// // next substring will start from operator end
		// splitOperators[splitOperatorsCount++] = currentToken;
		// }
		// }
		// break;
		// case TokenNameCOLON :
		// // : (15.24)
		// // see 1FK7C5R, we only split on a colon, when it is associated
		// // with a question-mark.
		// // indeed it might appear also behind a case statement, and we do
		// // not to break at this point.
		// if ((splitOperatorsCount == 0)
		// || splitOperators[splitOperatorsCount - 1] != TokenNameQUESTION) {
		// break;
		// }
		// case TokenNameextends :
		// case TokenNameimplements :
		// //case TokenNamethrows :
		// case TokenNameDOT :
		// // .
		// case TokenNameMULTIPLY :
		// // * (15.16.1)
		// case TokenNameDIVIDE :
		// // / (15.16.2)
		// case TokenNameREMAINDER :
		// // % (15.16.3)
		// case TokenNamePLUS :
		// // + (15.17, 15.17.2)
		// case TokenNameMINUS :
		// // - (15.17.2)
		// case TokenNameLEFT_SHIFT :
		// // << (15.18)
		// case TokenNameRIGHT_SHIFT :
		// // >> (15.18)
		// // case TokenNameUNSIGNED_RIGHT_SHIFT : // >>> (15.18)
		// case TokenNameLESS :
		// // < (15.19.1)
		// case TokenNameLESS_EQUAL :
		// // <= (15.19.1)
		// case TokenNameGREATER :
		// // > (15.19.1)
		// case TokenNameGREATER_EQUAL :
		// // >= (15.19.1)
		// // case TokenNameinstanceof : // instanceof
		// case TokenNameEQUAL_EQUAL :
		// // == (15.20, 15.20.1, 15.20.2, 15.20.3)
		// case TokenNameEQUAL_EQUAL_EQUAL :
		// // == (15.20, 15.20.1, 15.20.2, 15.20.3)
		// case TokenNameNOT_EQUAL :
		// // != (15.20, 15.20.1, 15.20.2, 15.20.3)
		// case TokenNameNOT_EQUAL_EQUAL :
		// // != (15.20, 15.20.1, 15.20.2, 15.20.3)
		// case TokenNameAND :
		// // & (15.21, 15.21.1, 15.21.2)
		// case TokenNameOR :
		// // | (15.21, 15.21.1, 15.21.2)
		// case TokenNameXOR :
		// // ^ (15.21, 15.21.1, 15.21.2)
		// case TokenNameAND_AND :
		// // && (15.22)
		// case TokenNameOR_OR :
		// // || (15.23)
		// case TokenNameQUESTION :
		// // ? (15.24)
		// case TokenNameMULTIPLY_EQUAL :
		// // *= (15.25.2)
		// case TokenNameDIVIDE_EQUAL :
		// // /= (15.25.2)
		// case TokenNameREMAINDER_EQUAL :
		// // %= (15.25.2)
		// case TokenNamePLUS_EQUAL :
		// // += (15.25.2)
		// case TokenNameMINUS_EQUAL :
		// // -= (15.25.2)
		// case TokenNameLEFT_SHIFT_EQUAL :
		// // <<= (15.25.2)
		// case TokenNameRIGHT_SHIFT_EQUAL :
		// // >>= (15.25.2)
		// // case TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL : // >>>= (15.25.2)
		// case TokenNameAND_EQUAL :
		// // &= (15.25.2)
		// case TokenNameXOR_EQUAL :
		// // ^= (15.25.2)
		// case TokenNameOR_EQUAL :
		// // |= (15.25.2)
		// if ((openParenthesisPositionCount < splitTokenDepth ||
		// (openParenthesisPositionCount == splitTokenDepth &&
		// splitTokenPriority
		// > getTokenPriority(currentToken)))
		// && !((currentToken == TokenNamePLUS || currentToken ==
		// TokenNameMINUS) && (previousToken == TokenNameLBRACE
		// || previousToken == TokenNameLBRACKET || splitScanner.startPosition
		// == 0))) {
		// // the current token is better than the one we currently have
		// // (in level or in priority if same level)
		// // reset the substringsCount
		// splitTokenDepth = openParenthesisPositionCount;
		// splitTokenType = currentToken;
		// splitTokenPriority = getTokenPriority(currentToken);
		// substringsStartPositions[0] = 0;
		// // better token means the whole line until now is the first
		// // substring
		// if (separateFirstArgumentOn(firstTokenOnLine)
		// && openParenthesisPositionCount > 0) {
		// substringsCount = 2; // resets the count of substrings
		// substringsEndPositions[0] = openParenthesisPosition[splitTokenDepth -
		// 1];
		// substringsStartPositions[1] = openParenthesisPosition[splitTokenDepth
		// - 1];
		// substringsEndPositions[1] = splitScanner.startPosition;
		// splitOperatorsCount = 3; // resets the count of split operators
		// splitOperators[0] = 0;
		// splitOperators[1] = 0;
		// splitOperators[2] = currentToken;
		// position = splitScanner.currentPosition;
		// // next substring will start from operator end
		// } else {
		// substringsCount = 1; // resets the count of substrings
		// substringsEndPositions[0] = splitScanner.startPosition;
		// // substring ends on operator start
		// position = splitScanner.currentPosition;
		// // next substring will start from operator end
		// splitOperatorsCount = 2; // resets the count of split operators
		// splitOperators[0] = 0;
		// // nothing for first operand since operator will be inserted in
		// // front of the second operand
		// splitOperators[1] = currentToken;
		// }
		// } else {
		// if (openParenthesisPositionCount == splitTokenDepth
		// && splitTokenPriority == getTokenPriority(currentToken)) {
		// // if another token with the same priority is found,
		// // push the start position of the substring and
		// // push the token into the stack.
		// // create a new array object if the current one is full.
		// if (substringsCount == substringsStartPositions.length) {
		// System
		// .arraycopy(
		// substringsStartPositions,
		// 0,
		// (substringsStartPositions = new int[substringsCount * 2]),
		// 0, substringsCount);
		// System.arraycopy(substringsEndPositions, 0,
		// (substringsEndPositions = new int[substringsCount * 2]),
		// 0, substringsCount);
		// }
		// if (splitOperatorsCount == splitOperators.length) {
		// System.arraycopy(splitOperators, 0,
		// (splitOperators = new int[splitOperatorsCount * 2]), 0,
		// splitOperatorsCount);
		// }
		// substringsStartPositions[substringsCount] = position;
		// substringsEndPositions[substringsCount++] =
		// splitScanner.startPosition;
		// // substring ends on operator start
		// position = splitScanner.currentPosition;
		// // next substring will start from operator end
		// splitOperators[splitOperatorsCount++] = currentToken;
		// }
		// }
		// default :
		// break;
		// }
		// if (isComment(currentToken)) {
		// lastCommentStartPosition = splitScanner.startPosition;
		// } else {
		// lastCommentStartPosition = -1;
		// }
		// }
		// } catch (InvalidInputException e) {
		// return null;
		// }
		// // if the string cannot be split, return null.
		// if (splitOperatorsCount == 0)
		// return null;
		// // ## SPECIAL CASES BEGIN
		// if (((splitOperatorsCount == 2 && splitOperators[1] == TokenNameDOT
		// && splitTokenDepth == 0 && lastOpenParenthesisPosition > -1)
		// || (splitOperatorsCount > 2 && splitOperators[1] == TokenNameDOT
		// && splitTokenDepth == 0 && lastOpenParenthesisPosition > -1 &&
		// lastOpenParenthesisPosition <= options.maxLineLength) ||
		// (separateFirstArgumentOn(firstTokenOnLine)
		// && splitTokenDepth > 0 && lastOpenParenthesisPosition > -1))
		// && (lastOpenParenthesisPosition < splitScanner.source.length &&
		// splitScanner.source[lastOpenParenthesisPosition] != ')')) {
		// // fix for 1FH4J2H: LFCOM:WINNT - Formatter - Empty parenthesis
		// should
		// // not be broken on two lines
		// // only one split on a top level .
		// // or more than one split on . and substring before open parenthesis
		// fits
		// // one line.
		// // or split inside parenthesis and first token is not a for/while/if
		// SplitLine sl = split(
		// stringToSplit.substring(lastOpenParenthesisPosition),
		// lastOpenParenthesisPosition);
		// if (sl == null || sl.operators[0] != TokenNameCOMMA) {
		// // trim() is used to remove the extra blanks at the end of the
		// // substring. See PR 1FGYPI1
		// return new SplitLine(new int[]{0, 0}, new String[]{
		// stringToSplit.substring(0, lastOpenParenthesisPosition).trim(),
		// stringToSplit.substring(lastOpenParenthesisPosition)}, new int[]{
		// offsetInGlobalLine,
		// lastOpenParenthesisPosition + offsetInGlobalLine});
		// } else {
		// // right substring can be split and is split on comma
		// // copy substrings and operators
		// // except if the 1st string is empty.
		// int startIndex = (sl.substrings[0].length() == 0) ? 1 : 0;
		// int subStringsLength = sl.substrings.length + 1 - startIndex;
		// String[] result = new String[subStringsLength];
		// int[] startIndexes = new int[subStringsLength];
		// int operatorsLength = sl.operators.length + 1 - startIndex;
		// int[] operators = new int[operatorsLength];
		// result[0] = stringToSplit.substring(0, lastOpenParenthesisPosition);
		// operators[0] = 0;
		// System.arraycopy(sl.startSubstringsIndexes, startIndex, startIndexes,
		// 1, subStringsLength - 1);
		// for (int i = subStringsLength - 1; i >= 0; i--) {
		// startIndexes[i] += offsetInGlobalLine;
		// }
		// System.arraycopy(sl.substrings, startIndex, result, 1,
		// subStringsLength - 1);
		// System.arraycopy(sl.operators, startIndex, operators, 1,
		// operatorsLength - 1);
		// return new SplitLine(operators, result, startIndexes);
		// }
		// }
		// // if the last token is a comment and the substring before the
		// comment fits
		// // on a line,
		// // split before the comment and return the result.
		// if (lastCommentStartPosition > -1
		// && lastCommentStartPosition < options.maxLineLength
		// && splitTokenPriority > 50) {
		// int end = lastCommentStartPosition;
		// int start = lastCommentStartPosition;
		// if (stringToSplit.charAt(end - 1) == ' ') {
		// end--;
		// }
		// if (start != end && stringToSplit.charAt(start) == ' ') {
		// start++;
		// }
		// return new SplitLine(new int[]{0, 0}, new String[]{
		// stringToSplit.substring(0, end), stringToSplit.substring(start)},
		// new int[]{0, start});
		// }
		// if (position != stringToSplit.length()) {
		// if (substringsCount == substringsStartPositions.length) {
		// System.arraycopy(substringsStartPositions, 0,
		// (substringsStartPositions = new int[substringsCount * 2]), 0,
		// substringsCount);
		// System.arraycopy(substringsEndPositions, 0,
		// (substringsEndPositions = new int[substringsCount * 2]), 0,
		// substringsCount);
		// }
		// // avoid empty extra substring, e.g. line terminated with a
		// semi-colon
		// substringsStartPositions[substringsCount] = position;
		// substringsEndPositions[substringsCount++] = stringToSplit.length();
		// }
		// if (splitOperatorsCount == splitOperators.length) {
		// System.arraycopy(splitOperators, 0,
		// (splitOperators = new int[splitOperatorsCount * 2]), 0,
		// splitOperatorsCount);
		// }
		// splitOperators[splitOperatorsCount] = 0;
		// // the last element of the stack is the position of the end of
		// // StringToSPlit
		// // +1 because the substring method excludes the last character
		// String[] result = new String[substringsCount];
		// for (int i = 0; i < substringsCount; i++) {
		// int start = substringsStartPositions[i];
		// int end = substringsEndPositions[i];
		// if (stringToSplit.charAt(start) == ' ') {
		// start++;
		// substringsStartPositions[i]++;
		// }
		// if (end != start && stringToSplit.charAt(end - 1) == ' ') {
		// end--;
		// }
		// result[i] = stringToSplit.substring(start, end);
		// substringsStartPositions[i] += offsetInGlobalLine;
		// }
		// if (splitOperatorsCount > substringsCount) {
		// System.arraycopy(substringsStartPositions, 0,
		// (substringsStartPositions = new int[splitOperatorsCount]), 0,
		// substringsCount);
		// System.arraycopy(substringsEndPositions, 0,
		// (substringsEndPositions = new int[splitOperatorsCount]), 0,
		// substringsCount);
		// for (int i = substringsCount; i < splitOperatorsCount; i++) {
		// substringsStartPositions[i] = position;
		// substringsEndPositions[i] = position;
		// }
		// System.arraycopy(splitOperators, 0,
		// (splitOperators = new int[splitOperatorsCount]), 0,
		// splitOperatorsCount);
		// } else {
		// System.arraycopy(substringsStartPositions, 0,
		// (substringsStartPositions = new int[substringsCount]), 0,
		// substringsCount);
		// System.arraycopy(substringsEndPositions, 0,
		// (substringsEndPositions = new int[substringsCount]), 0,
		// substringsCount);
		// System.arraycopy(splitOperators, 0,
		// (splitOperators = new int[substringsCount]), 0, substringsCount);
		// }
		// SplitLine splitLine = new SplitLine(splitOperators, result,
		// substringsStartPositions);
		// return splitLine;
	}

	private void updateMappedPositions(int startPosition) {
		if (positionsToMap == null) {
			return;
		}
		char[] source = scanner.source;
		int sourceLength = source.length;
		while (indexToMap < positionsToMap.length
				&& positionsToMap[indexToMap] <= startPosition) {
			int posToMap = positionsToMap[indexToMap];
			if (posToMap < 0 || posToMap >= sourceLength) {
				// protection against out of bounds position
				if (posToMap == sourceLength) {
					mappedPositions[indexToMap] = formattedSource.length();
				}
				indexToMap = positionsToMap.length; // no more mapping
				return;
			}
			if (CharOperation.isWhitespace(source[posToMap])) {
				mappedPositions[indexToMap] = startPosition + globalDelta
						+ lineDelta;
			} else {
				if (posToMap == sourceLength - 1) {
					mappedPositions[indexToMap] = startPosition + globalDelta
							+ lineDelta;
				} else {
					mappedPositions[indexToMap] = posToMap + globalDelta
							+ lineDelta;
				}
			}
			indexToMap++;
		}
	}

	private void updateMappedPositionsWhileSplitting(int startPosition,
			int endPosition) {
		if (mappedPositions == null || mappedPositions.length == indexInMap)
			return;
		while (indexInMap < mappedPositions.length
				&& startPosition <= mappedPositions[indexInMap]
				&& mappedPositions[indexInMap] < endPosition
				&& indexInMap < indexToMap) {
			mappedPositions[indexInMap] += splitDelta;
			indexInMap++;
		}
	}

	private int getLength(String s, int tabDepth) {
		int length = 0;
		for (int i = 0; i < tabDepth; i++) {
			length += options.tabSize;
		}
		for (int i = 0, max = s.length(); i < max; i++) {
			char currentChar = s.charAt(i);
			switch (currentChar) {
			case '\t':
				length += options.tabSize;
				break;
			default:
				length++;
			}
		}
		return length;
	}

	/**
	 * Sets the initial indentation level
	 * 
	 * @param indentationLevel
	 *            new indentation level
	 * 
	 * @deprecated
	 */
	public void setInitialIndentationLevel(int newIndentationLevel) {
		this.initialIndentationLevel = currentLineIndentationLevel = indentationLevel = newIndentationLevel;
	}

}