/*******************************************************************************
 * Copyright (c) 2000, 2011 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Ray V. (voidstar@gmail.com) - Contribution for bug 282988
 *******************************************************************************/
package org.eclipse.jdt.internal.formatter;

import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.parser.Scanner;
import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
import org.eclipse.jdt.internal.compiler.util.Util;
import org.eclipse.jdt.internal.core.util.CodeSnippetParsingUtil;
import org.eclipse.jdt.internal.core.util.RecordedParsingInformation;
import org.eclipse.jdt.internal.formatter.align.Alignment;
import org.eclipse.jdt.internal.formatter.align.AlignmentException;
import org.eclipse.jdt.internal.formatter.comment.CommentFormatterUtil;
import org.eclipse.jdt.internal.formatter.comment.HTMLEntity2JavaReader;
import org.eclipse.jdt.internal.formatter.comment.IJavaDocTagConstants;
import org.eclipse.jdt.internal.formatter.comment.Java2HTMLEntityReader;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultLineTracker;
import org.eclipse.jface.text.ILineTracker;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;

/**
 * This class is responsible for dumping formatted source
 * @since 2.1
 */
public class Scribe implements IJavaDocTagConstants {

	private static final int INITIAL_SIZE = 100;

	private boolean checkLineWrapping;
	/** one-based column */
	public int column;
	private int[][] commentPositions;

	// Most specific alignment.
	public Alignment currentAlignment;
	public int currentToken;

	// edits management
	private OptimizedReplaceEdit[] edits;
	public int editsIndex;

	public CodeFormatterVisitor formatter;
	public int indentationLevel;
	public int lastNumberOfNewLines;
	private boolean preserveLineBreakIndentation = false;
	public int line;

	private int[] lineEnds;
	private int maxLines;
	public Alignment memberAlignment;
	public boolean needSpace = false;

	// Line separator infos
	final private String lineSeparator;
	final private String lineSeparatorAndSpace;
	final private char firstLS;
	final private int lsLength;

	public int nlsTagCounter;
	public int pageWidth;
	public boolean pendingSpace = false;

	public Scanner scanner;
	public int scannerEndPosition;
	public int tabLength;
	public int indentationSize;
	private final IRegion[] regions;
	private IRegion[] adaptedRegions;
	public int tabChar;
	public int numberOfIndentations;
	private boolean useTabsOnlyForLeadingIndents;

	/** empty lines*/
	private final boolean indentEmptyLines;
	int blank_lines_between_import_groups = -1;

	// Preserve empty lines constants
	public static final int DO_NOT_PRESERVE_EMPTY_LINES = -1;
	public static final int PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION = 1;
	public static final int PRESERVE_EMPTY_LINES_IN_FORMAT_LEFT_CURLY_BRACE = 2;
	public static final int PRESERVE_EMPTY_LINES_IN_STRING_LITERAL_CONCATENATION = 3;
	public static final int PRESERVE_EMPTY_LINES_IN_CLOSING_ARRAY_INITIALIZER = 4;
	public static final int PRESERVE_EMPTY_LINES_IN_FORMAT_OPENING_BRACE = 5;
	public static final int PRESERVE_EMPTY_LINES_IN_BINARY_EXPRESSION = 6;
	public static final int PRESERVE_EMPTY_LINES_IN_EQUALITY_EXPRESSION = 7;
	public static final int PRESERVE_EMPTY_LINES_BEFORE_ELSE = 8;
	public static final int PRESERVE_EMPTY_LINES_IN_SWITCH_CASE = 9;
	public static final int PRESERVE_EMPTY_LINES_AT_END_OF_METHOD_DECLARATION = 10;
	public static final int PRESERVE_EMPTY_LINES_AT_END_OF_BLOCK = 11;
	final static int PRESERVE_EMPTY_LINES_DO_NOT_USE_ANY_INDENTATION = -1;
	final static int PRESERVE_EMPTY_LINES_USE_CURRENT_INDENTATION = 0;
	final static int PRESERVE_EMPTY_LINES_USE_TEMPORARY_INDENTATION = 1;

	/** disabling */
	boolean editsEnabled;
	boolean useTags;
	int tagsKind;

	/* Comments formatting */
	private static final int INCLUDE_BLOCK_COMMENTS = CodeFormatter.F_INCLUDE_COMMENTS | CodeFormatter.K_MULTI_LINE_COMMENT;
	private static final int INCLUDE_JAVA_DOC = CodeFormatter.F_INCLUDE_COMMENTS | CodeFormatter.K_JAVA_DOC;
	private static final int INCLUDE_LINE_COMMENTS = CodeFormatter.F_INCLUDE_COMMENTS | CodeFormatter.K_SINGLE_LINE_COMMENT;
	private static final int SKIP_FIRST_WHITESPACE_TOKEN = -2;
	private static final int INVALID_TOKEN = 2000;
	static final int NO_TRAILING_COMMENT = 0x0000;
	static final int BASIC_TRAILING_COMMENT = 0x0100;
	static final int COMPLEX_TRAILING_COMMENT = 0x0200;
	static final int IMPORT_TRAILING_COMMENT = COMPLEX_TRAILING_COMMENT | 0x0001;
	static final int UNMODIFIABLE_TRAILING_COMMENT = 0x0400;
	private int formatComments = 0;
	private int headerEndPosition = -1;
	String commentIndentation; // indentation requested in comments (usually in javadoc root tags description)

	// Class to store previous line comment information
	static class LineComment {
		boolean contiguous = false;
		int currentIndentation, indentation;
		int lines;
		char[] leadingSpaces;
	}
	final LineComment lastLineComment = new LineComment();

	// New way to format javadoc
	private FormatterCommentParser formatterCommentParser; // specialized parser to format comments

	// Disabling and enabling tags
	OptimizedReplaceEdit previousDisabledEdit;
	private char[] disablingTag, enablingTag;

	// Well know strings
	private String[] newEmptyLines = new String[10];
	private static String[] COMMENT_INDENTATIONS = new String[20];

	// final string buffers
	private final StringBuffer tempBuffer= new StringBuffer();
	private final StringBuffer blockCommentBuffer = new StringBuffer();
	private final StringBuffer blockCommentTokensBuffer = new StringBuffer();
	private final StringBuffer codeSnippetBuffer = new StringBuffer();
	private final StringBuffer javadocBlockRefBuffer= new StringBuffer();
	private final StringBuffer javadocGapLinesBuffer = new StringBuffer();
	private StringBuffer[] javadocHtmlTagBuffers = new StringBuffer[5];
	private final StringBuffer javadocTextBuffer = new StringBuffer();
	private final StringBuffer javadocTokensBuffer = new StringBuffer();

	Scribe(CodeFormatterVisitor formatter, long sourceLevel, IRegion[] regions, CodeSnippetParsingUtil codeSnippetParsingUtil, boolean includeComments) {
		initializeScanner(sourceLevel, formatter.preferences);
		this.formatter = formatter;
		this.pageWidth = formatter.preferences.page_width;
		this.tabLength = formatter.preferences.tab_size;
		this.indentationLevel= 0; // initialize properly
		this.numberOfIndentations = 0;
		this.useTabsOnlyForLeadingIndents = formatter.preferences.use_tabs_only_for_leading_indentations;
		this.indentEmptyLines = formatter.preferences.indent_empty_lines;
		this.tabChar = formatter.preferences.tab_char;
		if (this.tabChar == DefaultCodeFormatterOptions.MIXED) {
			this.indentationSize = formatter.preferences.indentation_size;
		} else {
			this.indentationSize = this.tabLength;
		}
		this.lineSeparator = formatter.preferences.line_separator;
		this.lineSeparatorAndSpace = this.lineSeparator+' ';
		this.firstLS = this.lineSeparator.charAt(0);
		this.lsLength = this.lineSeparator.length();
		this.indentationLevel = formatter.preferences.initial_indentation_level * this.indentationSize;
		this.regions= regions;
		if (codeSnippetParsingUtil != null) {
			final RecordedParsingInformation information = codeSnippetParsingUtil.recordedParsingInformation;
			if (information != null) {
				this.lineEnds = information.lineEnds;
				this.commentPositions = information.commentPositions;
			}
		}
		if (formatter.preferences.comment_format_line_comment) this.formatComments |= CodeFormatter.K_SINGLE_LINE_COMMENT;
		if (formatter.preferences.comment_format_block_comment) this.formatComments |= CodeFormatter.K_MULTI_LINE_COMMENT;
		if (formatter.preferences.comment_format_javadoc_comment) this.formatComments |= CodeFormatter.K_JAVA_DOC;
		if (includeComments) this.formatComments |= CodeFormatter.F_INCLUDE_COMMENTS;
		reset();
	}

	/**
	 * This method will adapt the selected regions if needed.
	 * If a region should be adapted (see isAdaptableRegion(IRegion))
	 * retrieve correct upper and lower bounds and replace the region.
	 */
	private void adaptRegions() {
		int max = this.regions.length;
		if (max == 1) {
			// It's not necessary to adapt the single region which covers all the source
			if (this.regions[0].getOffset() == 0 && this.regions[0].getLength() == this.scannerEndPosition) {
				this.adaptedRegions = this.regions;
				return;
			}
		}
		this.adaptedRegions = new IRegion[max];
		int commentIndex = 0;
		for (int i = 0; i < max; i++) {
			IRegion aRegion = this.regions[i];
			int offset = aRegion.getOffset();
			int length = aRegion.getLength();

			// First look if the region starts or ends inside a comment
			int index = getCommentIndex(commentIndex, offset);
			int adaptedOffset = offset;
			int adaptedLength = length;
			if (index >= 0) {
				// the offset of the region is inside a comment => restart the region from the comment start
				adaptedOffset = this.commentPositions[index][0];
				if (adaptedOffset >= 0) {
					// adapt only javadoc or block comments. Since fix for bug
					// https://bugs.eclipse.org/bugs/show_bug.cgi?id=238210
					// edits in line comments only concerns whitespaces hence can be
					// treated as edits in code
					adaptedLength = length + offset - adaptedOffset;
					commentIndex = index;
				}
			}
			index = getCommentIndex(commentIndex, offset+length-1);
			if (index >= 0 && this.commentPositions[index][0] >= 0) { // only javadoc or block comment
				// the region end is inside a comment => set the region end at the comment end
				int commentEnd = this.commentPositions[index][1];
				if (commentEnd < 0) commentEnd = -commentEnd;
				adaptedLength = commentEnd - adaptedOffset;
				commentIndex = index;
			}
			if (adaptedLength != length) {
				// adapt the region and jump to next one
				this.adaptedRegions[i] = new Region(adaptedOffset, adaptedLength);
			} else {
				this.adaptedRegions[i] = aRegion;
			}
		}
	}

	/*
	 * Adapt edits to regions.
	 * 
	 * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=234583"
	 * 	for more details
	 */
	private void adaptEdits() {

		// See if adapting edits is really necessary
		int max = this.regions.length;
		if (max == 1) {
			if (this.regions[0].getOffset() == 0 && this.regions[0].getLength() == this.scannerEndPosition) {
				// No need to adapt as the regions covers the whole source
				return;
			}
		}

		// Sort edits
		OptimizedReplaceEdit[] sortedEdits = new OptimizedReplaceEdit[this.editsIndex];
		System.arraycopy(this.edits, 0, sortedEdits, 0, this.editsIndex);
		Arrays.sort(sortedEdits, new Comparator() {
			public int compare(Object o1, Object o2) {
		    	OptimizedReplaceEdit edit1 = (OptimizedReplaceEdit) o1;
		    	OptimizedReplaceEdit edit2 = (OptimizedReplaceEdit) o2;
				return edit1.offset - edit2.offset;
            }
		});

		// Adapt overlapping edits
		int currentEdit = -1;
		for (int i = 0; i < max; i++) {
			IRegion region = this.adaptedRegions[i];
			int offset = region.getOffset();
			int length = region.getLength();

			// modify overlapping edits on the region (if any)
			int index = adaptEdit(sortedEdits, currentEdit, offset, offset+length);
			if (index != -1) {
				currentEdit = index;
			}
		}

    	// Set invalid all edits outside the region
		if (currentEdit != -1) {
			int length = sortedEdits.length;
	    	for (int e=currentEdit; e<length; e++) {
	    		sortedEdits[e].offset = -1;
	    	}
    	}
	}

	/*
     * Search whether a region overlap edit(s) at its start and/or at its end.
     * If so, modify the concerned edits to keep only the modifications which are
     * inside the given region.
     * 
     * The edit modification is done as follow:
     * 1) start it from the region start if it overlaps the region's start
     * 2) end it at the region end if it overlaps the region's end
     * 3) remove from the replacement string the number of lines which are outside
     * the region: before when overlapping region's start and after when overlapping
     * region's end. Note that the trailing indentation of the replacement string is not
     * kept when the region's end is overlapped because it's always outside the
     * region.
     */
    private int adaptEdit(OptimizedReplaceEdit[] sortedEdits, int start, int regionStart, int regionEnd) {
    	int initialStart = start==-1 ? 0 : start;
		int bottom = initialStart, top = sortedEdits.length - 1;
    	int topEnd = top;
    	int i = 0;
    	OptimizedReplaceEdit edit = null;
    	int overlapIndex = -1;

    	// Look for an edit overlapping the region start
    	while (bottom <= top) {
    		i = bottom + (top - bottom) /2;
    		edit = sortedEdits[i];
    		int editStart = edit.offset;
   			int editEnd = editStart + edit.length;
    		if (editStart > regionStart) {  // the edit starts after the region's start => no possible overlap of region's start
    			top = i-1;
    			if (editStart > regionEnd) { // the edit starts after the region's end => no possible overlap of region's end
    				topEnd = top;
    			}
    		} else {
    			if (editEnd < regionStart) { // the edit ends before the region's start => no possible overlap of region's start
	    			bottom = i+1;
				} else {
					// Count the lines of the edit which are outside the region
					int linesOutside = 0;
					StringBuffer spacesOutside = new StringBuffer();
					this.scanner.resetTo(editStart, editEnd-1);
					while (this.scanner.currentPosition < regionStart && !this.scanner.atEnd()) {
						char ch = (char) this.scanner.getNextChar();
						switch (ch) {
							case '\n':
								linesOutside++;
								spacesOutside.setLength(0);
								break;
							case '\r':
								break;
							default:
								spacesOutside.append(ch);
								break;
						}
					}

					// Restart the edit at the beginning of the line where the region start
					edit.offset = regionStart;
					int editLength = edit.length;
					edit.length -= edit.offset - editStart;

					// Cut replacement string if necessary
					int length = edit.replacement.length();
					if (length > 0) {

						// Count the lines in replacement string
						int linesReplaced = 0;
						for (int idx=0; idx < length; idx++) {
							if (edit.replacement.charAt(idx) == '\n') linesReplaced++;
						}

						// If the edit was a replacement but become an insertion due to the length reduction
						// and if the edit finishes just before the region starts and if there's no line to replace
						// then there's no replacement to do...
						if (editLength > 0 && edit.length == 0 && editEnd == regionStart && linesReplaced == 0 && linesOutside== 0) {
							edit.offset = -1;
						} else {

							// As the edit starts outside the region, remove first lines from edit string if any
							if (linesReplaced > 0) {
								int linesCount = linesOutside >= linesReplaced ? linesReplaced : linesOutside;
								if (linesCount > 0) {
									int idx = 0;
									loop: while (idx < length) {
										char ch = edit.replacement.charAt(idx);
										switch (ch) {
											case '\n':
												linesCount--;
												if (linesCount == 0) {
													idx++;
													break loop;
												}
												break;
											case '\r':
											case ' ':
											case '\t':
												break;
											default:
												break loop;
										}
										idx++;
									}
									// Compare spaces outside the region and the beginning
									// of the replacement string to remove the common part
									int spacesOutsideLength = spacesOutside.length();
									int replacementStart = idx;
									for (int o=0, r=0; o < spacesOutsideLength && r<(length-idx); o++) {
										char rch = edit.replacement.charAt(idx + r);
										char och = spacesOutside.charAt(o);
										if (rch == och) {
											replacementStart++;
											r++;
										} else if (rch == '\t' && (this.tabLength > 0 && och == ' ')) {
											if ((o+1)%this.tabLength == 0) {
												replacementStart++;
												r++;
											}
										} else {
											break;
										}
									}
									// Update the replacement string
									if (replacementStart > length || (replacementStart == length && spacesOutsideLength > 0)) {
										edit.offset = -1;
									} else if (spacesOutsideLength == 0 && replacementStart == length) {
										edit.replacement = ""; //$NON-NLS-1$
									} else {
										edit.replacement = edit.replacement.substring(replacementStart);
									}
								}
							}
						}
					}
					overlapIndex = i;
					break;
				}
			}
    	}
    	int validIndex = (overlapIndex != -1) ? overlapIndex : bottom;

    	// Look for an edit overlapping the region end
    	if (overlapIndex != -1) bottom = overlapIndex;
    	while (bottom <= topEnd) {
    		i = bottom + (topEnd - bottom) /2;
    		edit = sortedEdits[i];
    		int editStart = edit.offset;
   			int editEnd = editStart + edit.length;
   			if (regionEnd < editStart) {	// the edit starts after the region's end => no possible overlap of region's end
    			topEnd = i-1;
    		} else if (regionEnd == editStart) {	// special case when the edit starts just after the region's end...
    			// ...we got the last index of the edit inside the region
				topEnd = i - 1;
    			// this last edit is valid only if it's an insertion and if it has indentation
    			if (edit.length == 0) {
    				int nrLength = 0;
    				int rLength = edit.replacement.length();
    				if (nrLength < rLength) {
	    				int ch = edit.replacement.charAt(nrLength);
	    				loop: while (nrLength < rLength) {
		    				switch (ch) {
		    					case ' ':
		    					case '\t':
		    						nrLength++;
		    						break;
		    					default:
		    						break loop;
		    				}
	    				}
    				}
    				if (nrLength > 0) {
	    				topEnd++;
	    				if (nrLength < rLength) {
	    					edit.replacement = edit.replacement.substring(0, nrLength);
	    				}
    				}
    			}
    			break;
       		} else if (editEnd <= regionEnd) {	// the edit ends before the region's end => no possible overlap of region's end
    			bottom = i+1;
			} else {
				// Count the lines of the edit which are outside the region
				int linesOutside = 0;
				this.scanner.resetTo(editStart, editEnd-1);
				while (!this.scanner.atEnd()) {
					boolean after = this.scanner.currentPosition >= regionEnd;
                    char ch = (char) this.scanner.getNextChar();
                	if (ch == '\n' ) {
                		if (after) linesOutside++;
                	}
                }

				// Cut replacement string if necessary
				int length = edit.replacement.length();
				if (length > 0) {

					// Count the lines in replacement string
					int linesReplaced = 0;
					for (int idx=0; idx < length; idx++) {
						if (edit.replacement.charAt(idx) == '\n') linesReplaced++;
					}

					// Set the replacement string to the number of missing new lines
					// As the end of the edit is out of the region, the possible trailing
					// indentation should not be added...
					if (linesReplaced == 0) {
		    			edit.replacement = ""; //$NON-NLS-1$
					} else {
						int linesCount = linesReplaced > linesOutside ? linesReplaced - linesOutside : 0;
						if (linesCount == 0) {
			    			edit.replacement = ""; //$NON-NLS-1$
						} else {
							edit.replacement = getNewLineString(linesCount);
						}
					}
				}
				edit.length = regionEnd - editStart;

		    	// We got the last edit of the regions, give up
				topEnd = i;
				break;
			}
    	}

    	// Set invalid all edits outside the region
    	for (int e=initialStart; e<validIndex; e++) {
    		sortedEdits[e].offset = -1;
    	}
    	
    	// Return the index of next edit to look at
    	return topEnd+1;
    }

	private final void addDeleteEdit(int start, int end) {
		if (this.edits.length == this.editsIndex) {
			// resize
			resize();
		}
		addOptimizedReplaceEdit(start, end - start + 1, Util.EMPTY_STRING);
	}

	public final void addInsertEdit(int insertPosition, String insertedString) {
		if (this.edits.length == this.editsIndex) {
			// resize
			resize();
		}
		addOptimizedReplaceEdit(insertPosition, 0, insertedString);
	}

	private final void addOptimizedReplaceEdit(int offset, int length, String replacement) {
		if (!this.editsEnabled) {
			if (this.previousDisabledEdit != null && this.previousDisabledEdit.offset == offset) {
				replacement = this.previousDisabledEdit.replacement;
			}
			this.previousDisabledEdit = null;
			if (replacement.indexOf(this.lineSeparator) >= 0) {
				if (length == 0 || printNewLinesCharacters(offset, length)) {
					this.previousDisabledEdit = new OptimizedReplaceEdit(offset, length, replacement);
				}
			}
			return;
		}
		if (this.editsIndex > 0) {
			// try to merge last two edits
			final OptimizedReplaceEdit previous = this.edits[this.editsIndex-1];
			final int previousOffset = previous.offset;
			final int previousLength = previous.length;
			final int endOffsetOfPreviousEdit = previousOffset + previousLength;
			final int replacementLength = replacement.length();
			final String previousReplacement = previous.replacement;
			final int previousReplacementLength = previousReplacement.length();
			if (previousOffset == offset && previousLength == length && (replacementLength == 0 || previousReplacementLength == 0)) {
				if (this.currentAlignment != null) {
					final Location location = this.currentAlignment.location;
					if (location.editsIndex == this.editsIndex) {
						location.editsIndex--;
						location.textEdit = previous;
					}
				}
				this.editsIndex--;
				return;
			}
			if (endOffsetOfPreviousEdit == offset) {
				if (length != 0) {
					if (replacementLength != 0) {
						this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(previousOffset, previousLength + length, previousReplacement + replacement);
					} else if (previousLength + length == previousReplacementLength) {
						// check the characters. If they are identical, we can get rid of the previous edit
						boolean canBeRemoved = true;
						loop: for (int i = previousOffset; i < previousOffset + previousReplacementLength; i++) {
							if (this.scanner.source[i] != previousReplacement.charAt(i - previousOffset)) {
								this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(previousOffset, previousReplacementLength, previousReplacement);
								canBeRemoved = false;
								break loop;
							}
						}
						if (canBeRemoved) {
							if (this.currentAlignment != null) {
								final Location location = this.currentAlignment.location;
								if (location.editsIndex == this.editsIndex) {
									location.editsIndex--;
									location.textEdit = previous;
								}
							}
							this.editsIndex--;
						}
					} else {
						this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(previousOffset, previousLength + length, previousReplacement);
					}
				} else {
					if (replacementLength != 0) {
						this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(previousOffset, previousLength, previousReplacement + replacement);
					}
				}
			} else if ((offset + length == previousOffset) && (previousLength + length == replacementLength + previousReplacementLength)) {
				// check if both edits corresponds to the orignal source code
				boolean canBeRemoved = true;
				String totalReplacement = replacement + previousReplacement;
				loop: for (int i = 0; i < previousLength + length; i++) {
					if (this.scanner.source[i + offset] != totalReplacement.charAt(i)) {
						this.edits[this.editsIndex - 1] = new OptimizedReplaceEdit(offset, previousLength + length, totalReplacement);
						canBeRemoved = false;
						break loop;
					}
				}
				if (canBeRemoved) {
					if (this.currentAlignment != null) {
						final Location location = this.currentAlignment.location;
						if (location.editsIndex == this.editsIndex) {
							location.editsIndex--;
							location.textEdit = previous;
						}
					}
					this.editsIndex--;
				}
			} else {
				this.edits[this.editsIndex++] = new OptimizedReplaceEdit(offset, length, replacement);
			}
		} else {
			this.edits[this.editsIndex++] = new OptimizedReplaceEdit(offset, length, replacement);
		}
	}

	public final void addReplaceEdit(int start, int end, String replacement) {
		if (this.edits.length == this.editsIndex) {
			// resize
			resize();
		}
		addOptimizedReplaceEdit(start,  end - start + 1, replacement);
	}

	public void alignFragment(Alignment alignment, int fragmentIndex){
		alignment.fragmentIndex = fragmentIndex;
		alignment.checkColumn();
		alignment.performFragmentEffect();
	}

	public void checkNLSTag(int sourceStart) {
		if (hasNLSTag(sourceStart)) {
			this.nlsTagCounter++;
		}
	}

	private int consumeInvalidToken(int end) {
	    this.scanner.resetTo(this.scanner.startPosition, end);
    	// In case of invalid unicode character, consume the current backslash character before continuing
    	// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=233228
	    if (this.scanner.currentCharacter == '\\') {
	    	this.scanner.currentPosition = this.scanner.startPosition+1;
	    }
	    int previousPosition = this.scanner.currentPosition;
	    char ch = (char) this.scanner.getNextChar();
	    if (this.scanner.atEnd()) {
	    	// avoid infinite loop
	    	return INVALID_TOKEN;
	    }
	    while (!this.scanner.atEnd() && ch != '*' && !ScannerHelper.isWhitespace(ch)) {
	    	previousPosition = this.scanner.currentPosition;
	    	ch = (char) this.scanner.getNextChar();
	    }
	    // restore last whitespace
	    this.scanner.currentPosition = previousPosition;
	    return INVALID_TOKEN;
    }

	public Alignment createAlignment(int kind, int mode, int count, int sourceRestart){
		return createAlignment(kind, mode, Alignment.R_INNERMOST, count, sourceRestart);
	}

	public Alignment createAlignment(int kind, int mode, int tieBreakRule, int count, int sourceRestart){
		return createAlignment(kind, mode, tieBreakRule, count, sourceRestart, this.formatter.preferences.continuation_indentation, false);
	}

	public Alignment createAlignment(int kind, int mode, int count, int sourceRestart, int continuationIndent, boolean adjust){
		return createAlignment(kind, mode, Alignment.R_INNERMOST, count, sourceRestart, continuationIndent, adjust);
	}

	public Alignment createAlignment(int kind, int mode, int tieBreakRule, int count, int sourceRestart, int continuationIndent, boolean adjust){
		Alignment alignment = new Alignment(kind, mode, tieBreakRule, this, count, sourceRestart, continuationIndent);
		// specific break indentation for message arguments inside binary expressions
		if ((this.currentAlignment == null && this.formatter.expressionsDepth >= 0) ||
			(this.currentAlignment != null && this.currentAlignment.kind == Alignment.BINARY_EXPRESSION &&
				(this.formatter.expressionsPos & CodeFormatterVisitor.EXPRESSIONS_POS_MASK) == CodeFormatterVisitor.EXPRESSIONS_POS_BETWEEN_TWO)) {
			switch (kind) {
				case Alignment.CONDITIONAL_EXPRESSION:
				case Alignment.MESSAGE_ARGUMENTS:
				case Alignment.MESSAGE_SEND:
					if (this.formatter.lastBinaryExpressionAlignmentBreakIndentation == alignment.breakIndentationLevel) {
						alignment.breakIndentationLevel += this.indentationSize;
						alignment.shiftBreakIndentationLevel += this.indentationSize;
						this.formatter.lastBinaryExpressionAlignmentBreakIndentation = 0;
					}
					break;
			}
		}
		// adjust break indentation
		if (adjust && this.memberAlignment != null) {
			Alignment current = this.memberAlignment;
			while (current.enclosing != null) {
				current = current.enclosing;
			}
			if ((current.mode & Alignment.M_MULTICOLUMN) != 0) {
				final int indentSize = this.indentationSize;
				switch(current.chunkKind) {
					case Alignment.CHUNK_METHOD :
					case Alignment.CHUNK_TYPE :
						if ((mode & Alignment.M_INDENT_BY_ONE) != 0) {
							alignment.breakIndentationLevel = this.indentationLevel + indentSize;
						} else {
							alignment.breakIndentationLevel = this.indentationLevel + continuationIndent * indentSize;
						}
						alignment.update();
						break;
					case Alignment.CHUNK_FIELD :
						if ((mode & Alignment.M_INDENT_BY_ONE) != 0) {
							alignment.breakIndentationLevel = current.originalIndentationLevel + indentSize;
						} else {
							alignment.breakIndentationLevel = current.originalIndentationLevel + continuationIndent * indentSize;
						}
						alignment.update();
						break;
				}
			} else {
				switch(current.mode & Alignment.SPLIT_MASK) {
					case Alignment.M_COMPACT_SPLIT :
					case Alignment.M_COMPACT_FIRST_BREAK_SPLIT :
					case Alignment.M_NEXT_PER_LINE_SPLIT :
					case Alignment.M_NEXT_SHIFTED_SPLIT :
					case Alignment.M_ONE_PER_LINE_SPLIT :
						final int indentSize = this.indentationSize;
						switch(current.chunkKind) {
							case Alignment.CHUNK_METHOD :
							case Alignment.CHUNK_TYPE :
								if ((mode & Alignment.M_INDENT_BY_ONE) != 0) {
									alignment.breakIndentationLevel = this.indentationLevel + indentSize;
								} else {
									alignment.breakIndentationLevel = this.indentationLevel + continuationIndent * indentSize;
								}
								alignment.update();
								break;
							case Alignment.CHUNK_FIELD :
								if ((mode & Alignment.M_INDENT_BY_ONE) != 0) {
									alignment.breakIndentationLevel = current.originalIndentationLevel + indentSize;
								} else {
									alignment.breakIndentationLevel = current.originalIndentationLevel + continuationIndent * indentSize;
								}
								alignment.update();
								break;
						}
						break;
				}
			}
		}
		return alignment;
	}

	public Alignment createMemberAlignment(int kind, int mode, int count, int sourceRestart) {
		Alignment mAlignment = createAlignment(kind, mode, Alignment.R_INNERMOST, count, sourceRestart);
		mAlignment.breakIndentationLevel = this.indentationLevel;
		return mAlignment;
	}

	public void enterAlignment(Alignment alignment){
		alignment.enclosing = this.currentAlignment;
		alignment.location.lastLocalDeclarationSourceStart = this.formatter.lastLocalDeclarationSourceStart;
		this.currentAlignment = alignment;
	}

	public void enterMemberAlignment(Alignment alignment) {
		alignment.enclosing = this.memberAlignment;
		alignment.location.lastLocalDeclarationSourceStart = this.formatter.lastLocalDeclarationSourceStart;
		this.memberAlignment = alignment;
	}

	public void exitAlignment(Alignment alignment, boolean discardAlignment){
		Alignment current = this.currentAlignment;
		while (current != null){
			if (current == alignment) break;
			current = current.enclosing;
		}
		if (current == null) {
			throw new AbortFormatting("could not find matching alignment: "+alignment); //$NON-NLS-1$
		}
		this.indentationLevel = alignment.location.outputIndentationLevel;
		this.numberOfIndentations = alignment.location.numberOfIndentations;
		this.formatter.lastLocalDeclarationSourceStart = alignment.location.lastLocalDeclarationSourceStart;
		if (discardAlignment){
			this.currentAlignment = alignment.enclosing;
			if (this.currentAlignment == null) {
				this.formatter.lastBinaryExpressionAlignmentBreakIndentation = 0;
			}
		}
	}

	public void exitMemberAlignment(Alignment alignment){
		Alignment current = this.memberAlignment;
		while (current != null){
			if (current == alignment) break;
			current = current.enclosing;
		}
		if (current == null) {
			throw new AbortFormatting("could not find matching alignment: "+alignment); //$NON-NLS-1$
		}
		this.indentationLevel = current.location.outputIndentationLevel;
		this.numberOfIndentations = current.location.numberOfIndentations;
		this.formatter.lastLocalDeclarationSourceStart = alignment.location.lastLocalDeclarationSourceStart;
		this.memberAlignment = current.enclosing;
	}

	/**
	 * Answer actual indentation level based on true column position
	 * @return int
	 */
	public int getColumnIndentationLevel() {
		return this.column - 1;
	}

	public final int getCommentIndex(int position) {
		if (this.commentPositions == null)
			return -1;
		int length = this.commentPositions.length;
		if (length == 0) {
			return -1;
		}
		int g = 0, d = length - 1;
		int m = 0;
		while (g <= d) {
			m = g + (d - g) / 2;
			int bound = this.commentPositions[m][1];
			if (bound < 0) {
				bound = -bound;
			}
			if (bound < position) {
				g = m + 1;
			} else if (bound > position) {
				d = m - 1;
			} else {
				return m;
			}
		}
		return -(g + 1);
	}

	/*
     * Returns the index of the comment including the given offset position
     * starting the search from the given start index.
     *
     * @param start The start index for the research
     * @param position The position
     * @return The index of the comment if the given position is located inside it, -1 otherwise
     */
    private int getCommentIndex(int start, int position) {
    	int commentsLength = this.commentPositions == null ? 0 : this.commentPositions.length;
    	if (commentsLength == 0) return -1;
    	if (position == 0) {
    		if (commentsLength > 0 && this.commentPositions[0][0]== 0) {
    			return 0;
    		}
    		return -1;
    	}
    	int bottom = start, top = commentsLength - 1;
    	int i = 0;
    	int[] comment = null;
    	while (bottom <= top) {
    		i = bottom + (top - bottom) /2;
    		comment = this.commentPositions[i];
    		int commentStart = comment[0];
    		if (commentStart < 0) commentStart = -commentStart;
    		if (position < commentStart) {
    			top = i-1;
    		} else {
    			int commentEnd = comment[1];
    			if (commentEnd < 0) commentEnd = -commentEnd;
    			if (position >= commentEnd) {
    				bottom = i+1;
    			} else {
    				return i;
    			}
    		}
    	}
    	return -1;
    }

	private int getCurrentCommentIndentation(int start) {
		int linePtr = -Arrays.binarySearch(this.lineEnds, start);
		int indentation = 0;
		int beginningOfLine = getLineEnd(linePtr - 1)+1;
		if (beginningOfLine == -1) {
			beginningOfLine = 0;
		}
		int currentStartPosition = start;
		char[] source = this.scanner.source;

		// find the position of the beginning of the line containing the comment
		while (beginningOfLine > currentStartPosition) {
			if (linePtr > 0) {
				beginningOfLine = getLineEnd(--linePtr)+1;
			} else {
				beginningOfLine = 0;
				break;
			}
		}
		for (int i=beginningOfLine; i < currentStartPosition ; i++) {
			char currentCharacter = source[i];
			switch (currentCharacter) {
				case '\t' :
					if (this.tabLength != 0) {
						int reminder = indentation % this.tabLength;
						if (reminder == 0) {
							indentation += this.tabLength;
						} else {
							indentation = ((indentation / this.tabLength) + 1) * this.tabLength;
						}
					}
					break;
				case '\r' :
				case '\n' :
					indentation = 0;
					break;
				default:
					indentation++;
					break;
			}
		}
		return indentation;
	}

	int getCurrentIndentation(char[] whitespaces, int offset) {
		if (whitespaces == null) return offset;
		int length = whitespaces.length;
		if (this.tabLength == 0) return length;
		int indentation = offset;
		for (int i=0; i<length; i++) {
			char ch = whitespaces[i];
			switch (ch) {
				case '\t' :
					int reminder = indentation % this.tabLength;
					if (reminder == 0) {
						indentation += this.tabLength;
					} else {
						indentation = ((indentation / this.tabLength) + 1) * this.tabLength;
					}
					break;
				case '\r' :
				case '\n' :
					indentation = 0;
					break;
				default:
					indentation++;
					break;
			}
		}
		return indentation;
	}

	int getCurrentIndentation(int start) {
		int linePtr = Arrays.binarySearch(this.lineEnds, start);
		if (linePtr < 0) {
			linePtr = -linePtr - 1;
		}
		int indentation = 0;
		int beginningOfLine = getLineEnd(linePtr)+1;
		if (beginningOfLine == -1) {
			beginningOfLine = 0;
		}
		char[] source = this.scanner.source;

		for (int i=beginningOfLine; i<start; i++) {
			char currentCharacter = source[i];
			switch (currentCharacter) {
				case '\t' :
					if (this.tabLength != 0) {
						int reminder = indentation % this.tabLength;
						if (reminder == 0) {
							indentation += this.tabLength;
						} else {
							indentation = ((indentation / this.tabLength) + 1) * this.tabLength;
						}
					}
					break;
				case '\r' :
				case '\n' :
					indentation = 0;
					break;
				case ' ':
					indentation++;
					break;
				default:
					return indentation;
			}
		}
		return indentation;
	}

	public String getEmptyLines(int linesNumber) {
		if (this.nlsTagCounter > 0) {
			return Util.EMPTY_STRING;
		}
		String emptyLines;
		if (this.lastNumberOfNewLines == 0) {
			linesNumber++; // add an extra line breaks
			if (this.indentEmptyLines) {
				this.tempBuffer.setLength(0);
				for (int i = 0; i < linesNumber; i++) {
					printIndentationIfNecessary(this.tempBuffer);
					this.tempBuffer.append(this.lineSeparator);
					this.column = 1;
				}
				emptyLines = this.tempBuffer.toString();
			} else {
				emptyLines = getNewLineString(linesNumber);
			}
			this.lastNumberOfNewLines += linesNumber;
			this.line += linesNumber;
			this.column = 1;
			this.needSpace = false;
			this.pendingSpace = false;
		} else if (this.lastNumberOfNewLines == 1) {
			if (this.indentEmptyLines) {
				this.tempBuffer.setLength(0);
				for (int i = 0; i < linesNumber; i++) {
					printIndentationIfNecessary(this.tempBuffer);
					this.tempBuffer.append(this.lineSeparator);
					this.column = 1;
				}
				emptyLines = this.tempBuffer.toString();
			} else {
				emptyLines = getNewLineString(linesNumber);
			}
			this.lastNumberOfNewLines += linesNumber;
			this.line += linesNumber;
			this.column = 1;
			this.needSpace = false;
			this.pendingSpace = false;
		} else {
			if ((this.lastNumberOfNewLines - 1) >= linesNumber) {
				// there is no need to add new lines
				return Util.EMPTY_STRING;
			}
			final int realNewLineNumber = linesNumber - this.lastNumberOfNewLines + 1;
			if (this.indentEmptyLines) {
				this.tempBuffer.setLength(0);
				for (int i = 0; i < realNewLineNumber; i++) {
					printIndentationIfNecessary(this.tempBuffer);
					this.tempBuffer.append(this.lineSeparator);
					this.column = 1;
				}
				emptyLines = this.tempBuffer.toString();
			} else {
				emptyLines = getNewLineString(realNewLineNumber);
			}
			this.lastNumberOfNewLines += realNewLineNumber;
			this.line += realNewLineNumber;
			this.column = 1;
			this.needSpace = false;
			this.pendingSpace = false;
		}
		return emptyLines;
	}

	public OptimizedReplaceEdit getLastEdit() {
		if (this.editsIndex > 0) {
			return this.edits[this.editsIndex - 1];
		}
		return null;
	}

	public final int getLineEnd(int lineNumber) {
		if (this.lineEnds == null)
			return -1;
		if (lineNumber >= this.lineEnds.length + 1)
			return this.scannerEndPosition;
		if (lineNumber <= 0)
			return -1;
		return this.lineEnds[lineNumber-1]; // next line start one character behind the lineEnd of the previous line
	}

	Alignment getMemberAlignment() {
		return this.memberAlignment;
	}

	public String getNewLine() {
		if (this.nlsTagCounter > 0) {
			return Util.EMPTY_STRING;
		}
		if (this.lastNumberOfNewLines >= 1) {
			this.column = 1; // ensure that the scribe is at the beginning of a new line
			return Util.EMPTY_STRING;
		}
		this.line++;
		this.lastNumberOfNewLines = 1;
		this.column = 1;
		this.needSpace = false;
		this.pendingSpace = false;
		return this.lineSeparator;
	}

	private String getNewLineString(int linesCount) {
		int length = this.newEmptyLines.length;
		if (linesCount > length) {
			System.arraycopy(this.newEmptyLines, 0, this.newEmptyLines = new String[linesCount+10], 0, length);
		}
		String newLineString = this.newEmptyLines[linesCount-1];
		if (newLineString == null) {
			this.tempBuffer.setLength(0);
			for (int j=0; j<linesCount; j++) {
				this.tempBuffer.append(this.lineSeparator);
			}
			newLineString = this.tempBuffer.toString();
			this.newEmptyLines[linesCount-1] = newLineString;
		}
		return newLineString;
	}

	/**
	 * Answer next indentation level based on column estimated position
	 * (if column is not indented, then use indentationLevel)
	 */
	public int getNextIndentationLevel(int someColumn) {
		int indent = someColumn - 1;
		if (indent == 0)
			return this.indentationLevel;
		if (this.tabChar == DefaultCodeFormatterOptions.TAB) {
			if (this.useTabsOnlyForLeadingIndents) {
				return indent;
			}
			if (this.indentationSize == 0) {
				return indent;
			}
			int rem = indent % this.indentationSize;
			int addition = rem == 0 ? 0 : this.indentationSize - rem; // round to superior
			return indent + addition;
		}
		return indent;
	}

	/*
	 * Preserve empty lines depending on given count and preferences.
	 */
	private String getPreserveEmptyLines(int count, int emptyLinesRules) {
		if (count == 0) {
			int currentIndentationLevel = this.indentationLevel;
			int useAlignmentBreakIndentation = useAlignmentBreakIndentation(emptyLinesRules);
			switch (useAlignmentBreakIndentation) {
				case PRESERVE_EMPTY_LINES_DO_NOT_USE_ANY_INDENTATION:
					return Util.EMPTY_STRING;
				default:
					// Return the new indented line
					StringBuffer buffer = new StringBuffer(getNewLine());
					printIndentationIfNecessary(buffer);
					if (useAlignmentBreakIndentation == PRESERVE_EMPTY_LINES_USE_TEMPORARY_INDENTATION) {
						this.indentationLevel = currentIndentationLevel;
					}
					return buffer.toString();
			}
		}
		if (this.blank_lines_between_import_groups >= 0) {
			useAlignmentBreakIndentation(emptyLinesRules);
			return getEmptyLines(this.blank_lines_between_import_groups);
		}
		if (this.formatter.preferences.number_of_empty_lines_to_preserve != 0) {
			useAlignmentBreakIndentation(emptyLinesRules);
			int linesToPreserve = Math.min(count, this.formatter.preferences.number_of_empty_lines_to_preserve);
			return getEmptyLines(linesToPreserve);
		}
		return getNewLine();
	}
	private int useAlignmentBreakIndentation(int emptyLinesRules) {
		// preserve line breaks in wrapping if specified
		// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=198074
		boolean specificEmptyLinesRule = emptyLinesRules != PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION;
		if ((this.currentAlignment != null || specificEmptyLinesRule) && !this.formatter.preferences.join_wrapped_lines) {
			// insert a new line only if it has not been already done before
			// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=283476
			if (this.lastNumberOfNewLines == 0 || specificEmptyLinesRule || this.formatter.arrayInitializersDepth >= 0) {
				
				// Do not use alignment break indentation in specific circumstances
				boolean useAlignmentBreakIndentation;
				boolean useAlignmentShiftBreakIndentation = false;
				boolean useLastBinaryExpressionAlignmentBreakIndentation = false;
				switch (emptyLinesRules) {
					case DO_NOT_PRESERVE_EMPTY_LINES:
					case PRESERVE_EMPTY_LINES_IN_SWITCH_CASE:
					case PRESERVE_EMPTY_LINES_AT_END_OF_METHOD_DECLARATION:
					case PRESERVE_EMPTY_LINES_AT_END_OF_BLOCK:
						return PRESERVE_EMPTY_LINES_DO_NOT_USE_ANY_INDENTATION;
					case PRESERVE_EMPTY_LINES_IN_BINARY_EXPRESSION:
						useAlignmentBreakIndentation = true;
						if ((this.formatter.expressionsPos & CodeFormatterVisitor.EXPRESSIONS_POS_MASK) == CodeFormatterVisitor.EXPRESSIONS_POS_BETWEEN_TWO) {
							// we're just before the left expression, try to use the last
							// binary expression break indentation if any
							useLastBinaryExpressionAlignmentBreakIndentation = true;
						}
						break;
					case PRESERVE_EMPTY_LINES_IN_EQUALITY_EXPRESSION:
						useAlignmentShiftBreakIndentation = this.currentAlignment == null || this.currentAlignment.kind == Alignment.BINARY_EXPRESSION;
						useAlignmentBreakIndentation = !useAlignmentShiftBreakIndentation;
						break;
					case PRESERVE_EMPTY_LINES_IN_FORMAT_OPENING_BRACE:
						useAlignmentBreakIndentation = this.formatter.arrayInitializersDepth <= 1
							&& this.currentAlignment != null
							&& this.currentAlignment.kind == Alignment.ARRAY_INITIALIZER;
						break;
					case PRESERVE_EMPTY_LINES_IN_FORMAT_LEFT_CURLY_BRACE:
						useAlignmentBreakIndentation = false;
						break;
					default:
						if ((emptyLinesRules & 0xFFFF) == PRESERVE_EMPTY_LINES_IN_CLOSING_ARRAY_INITIALIZER && this.scanner.currentCharacter == '}' ) {
							// last array initializer closing brace
							this.indentationLevel = emptyLinesRules >> 16;
							this.preserveLineBreakIndentation = true;
							return PRESERVE_EMPTY_LINES_USE_CURRENT_INDENTATION;
						}
						useAlignmentBreakIndentation = true;
						break;
				}

				// If there's an alignment try to align on its break indentation level
				Alignment alignment = this.currentAlignment;
				if (alignment == null) {
					if (useLastBinaryExpressionAlignmentBreakIndentation) {
						if (this.indentationLevel < this.formatter.lastBinaryExpressionAlignmentBreakIndentation) {
							this.indentationLevel = this.formatter.lastBinaryExpressionAlignmentBreakIndentation;
						}
					}
					if (useAlignmentShiftBreakIndentation && this.memberAlignment != null) {
						if (this.indentationLevel < this.memberAlignment.shiftBreakIndentationLevel) {
							this.indentationLevel = this.memberAlignment.shiftBreakIndentationLevel;
						}
					}
				} else {
					// Use the member alignment break indentation level when
					// it's closer from the wrapped line than the current alignment
					if (this.memberAlignment != null && this.memberAlignment.location.inputOffset > alignment.location.inputOffset) {
						alignment = this.memberAlignment;
					}

					// Use the break indentation level if possible...
					if (useLastBinaryExpressionAlignmentBreakIndentation) {
						if (this.indentationLevel < this.formatter.lastBinaryExpressionAlignmentBreakIndentation) {
							this.indentationLevel = this.formatter.lastBinaryExpressionAlignmentBreakIndentation;
						}
					}
					if (useAlignmentBreakIndentation) {
						if (this.indentationLevel < alignment.breakIndentationLevel) {
							this.indentationLevel = alignment.breakIndentationLevel;
						}
					} else if (useAlignmentShiftBreakIndentation) {
						if (this.indentationLevel < alignment.shiftBreakIndentationLevel) {
							this.indentationLevel = alignment.shiftBreakIndentationLevel;
						}
					}
				}
				this.preserveLineBreakIndentation = true;
				if (useLastBinaryExpressionAlignmentBreakIndentation || useAlignmentShiftBreakIndentation) {
					return PRESERVE_EMPTY_LINES_USE_TEMPORARY_INDENTATION;
				}
				return PRESERVE_EMPTY_LINES_USE_CURRENT_INDENTATION;
			}
		}
		return PRESERVE_EMPTY_LINES_DO_NOT_USE_ANY_INDENTATION;
	}

	public TextEdit getRootEdit() {
		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=208541
		adaptRegions();
		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=234583
		adaptEdits();

		MultiTextEdit edit = null;
		int regionsLength = this.adaptedRegions.length;
		int textRegionStart;
		int textRegionEnd;
		if (regionsLength == 1) {
			IRegion lastRegion = this.adaptedRegions[0];
			textRegionStart = lastRegion.getOffset();
			textRegionEnd = textRegionStart + lastRegion.getLength();
		} else {
			textRegionStart = this.adaptedRegions[0].getOffset();
			IRegion lastRegion = this.adaptedRegions[regionsLength - 1];
			textRegionEnd = lastRegion.getOffset() + lastRegion.getLength();
		}

		int length = textRegionEnd - textRegionStart + 1;
		if (textRegionStart <= 0) {
			if (length <= 0) {
				edit = new MultiTextEdit(0, 0);
			} else {
				edit = new MultiTextEdit(0, textRegionEnd);
			}
		} else {
			edit = new MultiTextEdit(textRegionStart, length - 1);
		}
		for (int i= 0, max = this.editsIndex; i < max; i++) {
			OptimizedReplaceEdit currentEdit = this.edits[i];
			if (currentEdit.offset >= 0 && currentEdit.offset <= this.scannerEndPosition) {
				if (currentEdit.length == 0 || (currentEdit.offset != this.scannerEndPosition && isMeaningfulEdit(currentEdit))) {
					try {
						edit.addChild(new ReplaceEdit(currentEdit.offset, currentEdit.length, currentEdit.replacement));
					}
					catch (MalformedTreeException ex) {
						// log exception in case of error
						CommentFormatterUtil.log(ex);
	 					throw ex;
					}
				}
			}
		}
		this.edits = null;
		return edit;
	}

	public void handleLineTooLong() {
		if (this.formatter.preferences.wrap_outer_expressions_when_nested) {
			handleLineTooLongSmartly();
			return;
		}
		// search for closest breakable alignment, using tiebreak rules
		// look for outermost breakable one
		int relativeDepth = 0, outerMostDepth = -1;
		Alignment targetAlignment = this.currentAlignment;
		while (targetAlignment != null){
			if (targetAlignment.tieBreakRule == Alignment.R_OUTERMOST && targetAlignment.couldBreak()){
				outerMostDepth = relativeDepth;
			}
			targetAlignment = targetAlignment.enclosing;
			relativeDepth++;
		}
		if (outerMostDepth >= 0) {
			throw new AlignmentException(AlignmentException.LINE_TOO_LONG, outerMostDepth);
		}
		// look for innermost breakable one
		relativeDepth = 0;
		targetAlignment = this.currentAlignment;
		while (targetAlignment != null){
			if (targetAlignment.couldBreak()){
				throw new AlignmentException(AlignmentException.LINE_TOO_LONG, relativeDepth);
			}
			targetAlignment = targetAlignment.enclosing;
			relativeDepth++;
		}
		// did not find any breakable location - proceed
	}

	private void handleLineTooLongSmartly() {
		// search for closest breakable alignment, using tiebreak rules
		// look for outermost breakable one
		int relativeDepth = 0, outerMostDepth = -1;
		Alignment targetAlignment = this.currentAlignment;
		int previousKind = -1;
		int insideMessage = 0;
		boolean insideStringConcat = false;
		while (targetAlignment != null){
			boolean couldBreak = targetAlignment.tieBreakRule == Alignment.R_OUTERMOST ||
				(!insideStringConcat &&
						insideMessage > 0 && targetAlignment.kind == Alignment.MESSAGE_ARGUMENTS &&
						(!targetAlignment.wasReset() || previousKind != Alignment.MESSAGE_SEND));
			if (couldBreak && targetAlignment.couldBreak()){
				outerMostDepth = relativeDepth;
			}
			switch (targetAlignment.kind) {
				case Alignment.MESSAGE_ARGUMENTS:
				case Alignment.MESSAGE_SEND:
					insideMessage++;
					break;
				case Alignment.STRING_CONCATENATION:
					insideStringConcat = true;
					break;
			}
			previousKind = targetAlignment.kind;
			targetAlignment = targetAlignment.enclosing;
			relativeDepth++;
		}
		if (outerMostDepth >= 0) {
			throw new AlignmentException(AlignmentException.LINE_TOO_LONG, outerMostDepth);
		}
		// look for innermost breakable one
		relativeDepth = 0;
		targetAlignment = this.currentAlignment;
		AlignmentException alignmentException = null;
		int msgArgsDepth = -1;
		while (targetAlignment != null) {
			if (targetAlignment.kind == Alignment.MESSAGE_ARGUMENTS) {
				msgArgsDepth = relativeDepth;
			}
			if (alignmentException == null) {
				if (targetAlignment.couldBreak()) {
					// do not throw the exception immediately to have a chance to reset
					// previously broken alignments (see bug 203588)
					alignmentException = new AlignmentException(AlignmentException.LINE_TOO_LONG, relativeDepth);
					if (insideStringConcat) throw alignmentException;
				}
			} else if (targetAlignment.wasSplit) {
				// reset the nearest already broken outermost alignment.
				// Note that it's not done twice to avoid infinite loop while raising
				// the exception on an innermost alignment...
				if (!targetAlignment.wasReset()) {
					targetAlignment.reset();
					if (msgArgsDepth > alignmentException.relativeDepth) {
						alignmentException.relativeDepth = msgArgsDepth;
					}
					throw alignmentException;
				}
			}
			targetAlignment = targetAlignment.enclosing;
			relativeDepth++;
		}
		if (alignmentException != null) {
			throw alignmentException;
		}
		// did not find any breakable location - proceed
		if (this.currentAlignment != null) {
			this.currentAlignment.blockAlign = false;
			this.currentAlignment.tooLong = true;
		}
	}

	/*
	 * Check if there is a NLS tag on this line. If yes, return true, returns false otherwise.
	 */
	private boolean hasNLSTag(int sourceStart) {
		// search the last comment where commentEnd < current lineEnd
		if (this.lineEnds == null) return false;
		int index = Arrays.binarySearch(this.lineEnds, sourceStart);
		int currentLineEnd = getLineEnd(-index);
		if (currentLineEnd != -1) {
			int commentIndex = getCommentIndex(currentLineEnd);
			if (commentIndex < 0) {
				commentIndex = -commentIndex - 2;
			}
			if (commentIndex >= 0 && commentIndex < this.commentPositions.length) {
				int start = this.commentPositions[commentIndex][0];
				if (start < 0) {
					start = -start;
					// check that we are on the same line
					int lineIndexForComment = Arrays.binarySearch(this.lineEnds, start);
					if (lineIndexForComment == index) {
						return CharOperation.indexOf(Scanner.TAG_PREFIX, this.scanner.source, true, start, currentLineEnd) != -1;
					}
				}
			}
		}
		return false;
	}

	private boolean includesBlockComments() {
	    return ((this.formatComments & INCLUDE_BLOCK_COMMENTS) == INCLUDE_BLOCK_COMMENTS && this.headerEndPosition < this.scanner.currentPosition) ||
	    	(this.formatter.preferences.comment_format_header && this.headerEndPosition >= this.scanner.currentPosition);
    }

	private boolean includesJavadocComments() {
	    return ((this.formatComments & INCLUDE_JAVA_DOC) == INCLUDE_JAVA_DOC && this.headerEndPosition < this.scanner.currentPosition) ||
	    	(this.formatter.preferences.comment_format_header && this.headerEndPosition >= this.scanner.currentPosition);
    }

	private boolean includesLineComments() {
	    return ((this.formatComments & INCLUDE_LINE_COMMENTS) == INCLUDE_LINE_COMMENTS && this.headerEndPosition < this.scanner.currentPosition) ||
	    	(this.formatter.preferences.comment_format_header && this.headerEndPosition >= this.scanner.currentPosition);
    }

	boolean includesComments() {
	    return (this.formatComments & CodeFormatter.F_INCLUDE_COMMENTS) != 0;
    }

	public void indent() {
		this.indentationLevel += this.indentationSize;
		this.numberOfIndentations++;
	}

	void setIndentation(int level, int n) {
		this.indentationLevel = level + n * this.indentationSize;
		this.numberOfIndentations = this.indentationLevel / this.indentationSize;
	}

	private void initializeScanner(long sourceLevel, DefaultCodeFormatterOptions preferences) {
		this.useTags = preferences.use_tags;
		this.tagsKind = 0;
		char[][] taskTags = null;
		if (this.useTags) {
			this.disablingTag = preferences.disabling_tag;
			this.enablingTag = preferences.enabling_tag;
			if (this.disablingTag == null) {
				if (this.enablingTag != null) {
					taskTags = new char[][] { this.enablingTag };
				}
			} else if (this.enablingTag == null) {
				taskTags = new char[][] { this.disablingTag };
			} else {
				taskTags = new char[][] { this.disablingTag, this.enablingTag };
			}
		}
		if (taskTags != null) {
			loop: for (int i=0,length=taskTags.length; i<length; i++) {
				if (taskTags[i].length > 2 && taskTags[i][0] == '/') {
					switch (taskTags[i][1]) {
						case '/':
							this.tagsKind = TerminalTokens.TokenNameCOMMENT_LINE;
							break loop;
						case '*':
							if (taskTags[i][2] != '*') {
								this.tagsKind = TerminalTokens.TokenNameCOMMENT_BLOCK;
								break loop;
							}
							break;
					}
				}
			}
		}
		this.scanner = new Scanner(true, true, false/*nls*/, sourceLevel/*sourceLevel*/, taskTags, null/*taskPriorities*/, true/*taskCaseSensitive*/);
		this.editsEnabled = true;
	}

	private void initFormatterCommentParser() {
		if (this.formatterCommentParser == null) {
			this.formatterCommentParser = new FormatterCommentParser(this.scanner.sourceLevel);
		}
		this.formatterCommentParser.scanner.setSource(this.scanner.source);
		this.formatterCommentParser.source = this.scanner.source;
		this.formatterCommentParser.scanner.lineEnds = this.lineEnds;
		this.formatterCommentParser.scanner.linePtr = this.maxLines;
		this.formatterCommentParser.parseHtmlTags = this.formatter.preferences.comment_format_html;
	}

	private boolean isOnFirstColumn(int start) {
		if (this.lineEnds == null) return start == 0;
		int index = Arrays.binarySearch(this.lineEnds, start);
		// we want the line end of the previous line
		int previousLineEnd = getLineEnd(-index - 1);
		return previousLineEnd != -1 && previousLineEnd == start - 1;
	}

	private boolean isMeaningfulEdit(OptimizedReplaceEdit edit) {
		final int editLength= edit.length;
		final int editReplacementLength= edit.replacement.length();
		final int editOffset= edit.offset;
		if (editReplacementLength != 0 && editLength == editReplacementLength) {
			for (int i = editOffset, max = editOffset + editLength; i < max; i++) {
				if (this.scanner.source[i] != edit.replacement.charAt(i - editOffset)) {
					return true;
				}
			}
			return false;
		}
		return true;
	}

	private void preserveEmptyLines(int count, int insertPosition) {
		if (count > 0) {
			if (this.blank_lines_between_import_groups >= 0) {
				printEmptyLines(this.blank_lines_between_import_groups, insertPosition);
			} else if (this.formatter.preferences.number_of_empty_lines_to_preserve != 0) {
				int linesToPreserve = Math.min(count, this.formatter.preferences.number_of_empty_lines_to_preserve);
				printEmptyLines(linesToPreserve, insertPosition);
			} else {
				printNewLine(insertPosition);
			}
		}
	}

	private void print(int length, boolean considerSpaceIfAny) {
		if (this.checkLineWrapping && length + this.column > this.pageWidth) {
			handleLineTooLong();
		}
		this.lastNumberOfNewLines = 0;
		if (this.indentationLevel != 0) {
			printIndentationIfNecessary();
		}
		if (considerSpaceIfAny) {
			space();
		}
		if (this.pendingSpace) {
			addInsertEdit(this.scanner.getCurrentTokenStartPosition(), " "); //$NON-NLS-1$
		}
		this.pendingSpace = false;
		this.needSpace = false;
		this.column += length;
		this.needSpace = true;
	}

	private void printBlockComment(boolean isJavadoc) {
		int currentTokenStartPosition = this.scanner.getCurrentTokenStartPosition();
		int currentTokenEndPosition = this.scanner.getCurrentTokenEndPosition() + 1;
		boolean includesBlockComments = !isJavadoc && includesBlockComments();

		this.scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition - 1);
		int currentCharacter;
		boolean isNewLine = false;
		int start = currentTokenStartPosition;
		int nextCharacterStart = currentTokenStartPosition;
		int previousStart = currentTokenStartPosition;
		boolean onFirstColumn = isOnFirstColumn(start);

		boolean indentComment = false;
		if (this.indentationLevel != 0) {
			if (isJavadoc
					|| !this.formatter.preferences.never_indent_block_comments_on_first_column
					|| !onFirstColumn) {
				indentComment = true;
				printIndentationIfNecessary();
			}
		}
		if (this.pendingSpace) {
			addInsertEdit(currentTokenStartPosition, " "); //$NON-NLS-1$
		}
		this.needSpace = false;
		this.pendingSpace = false;

		int commentColumn = this.column;
		if (includesBlockComments) {
			if (printBlockComment(currentTokenStartPosition, currentTokenEndPosition)) {
				return;
			}
		}

		int currentIndentationLevel = this.indentationLevel;
		if ((commentColumn-1) > this.indentationLevel) {
			this.indentationLevel = commentColumn-1;
		}
		int currentCommentIndentation = onFirstColumn ? 0 : getCurrentCommentIndentation(start);
		boolean formatComment = (isJavadoc && (this.formatComments & CodeFormatter.K_JAVA_DOC) != 0) || (!isJavadoc && (this.formatComments & CodeFormatter.K_MULTI_LINE_COMMENT) != 0);

		try {
			while (nextCharacterStart <= currentTokenEndPosition && (currentCharacter = this.scanner.getNextChar()) != -1) {
				nextCharacterStart = this.scanner.currentPosition;

				switch(currentCharacter) {
					case '\r' :
						start = previousStart;
						isNewLine = true;
						if (this.scanner.getNextChar('\n')) {
							currentCharacter = '\n';
							nextCharacterStart = this.scanner.currentPosition;
						}
						break;
					case '\n' :
						start = previousStart;
						isNewLine = true;
						nextCharacterStart = this.scanner.currentPosition;
						break;
					default:
						if (isNewLine) {
							this.column = 1;
							this.line++;
							isNewLine = false;

							boolean addSpace = false;
							if (onFirstColumn) {
								if (formatComment) {
									if (ScannerHelper.isWhitespace((char) currentCharacter)) {
										int previousStartPosition = this.scanner.currentPosition;
										while(currentCharacter != -1 && currentCharacter != '\r' && currentCharacter != '\n' && ScannerHelper.isWhitespace((char) currentCharacter)) {
											previousStart = nextCharacterStart;
											previousStartPosition = this.scanner.currentPosition;
											currentCharacter = this.scanner.getNextChar();
											nextCharacterStart = this.scanner.currentPosition;
										}
										if (currentCharacter == '\r' || currentCharacter == '\n') {
											nextCharacterStart = previousStartPosition;
										}
									}
									if (currentCharacter != '\r' && currentCharacter != '\n') {
										addSpace = true;
									}
								}
							} else {
								if (ScannerHelper.isWhitespace((char) currentCharacter)) {
									int previousStartPosition = this.scanner.currentPosition;
									int currentIndentation = 0;
									loop: while(currentCharacter != -1 && currentCharacter != '\r' && currentCharacter != '\n' && ScannerHelper.isWhitespace((char) currentCharacter)) {
										if (currentIndentation >= currentCommentIndentation) {
											break loop;
										}
										previousStart = nextCharacterStart;
										previousStartPosition = this.scanner.currentPosition;
										switch(currentCharacter) {
											case '\t' :
												if (this.tabLength != 0) {
													int reminder = currentIndentation % this.tabLength;
													if (reminder == 0) {
														currentIndentation += this.tabLength;
													} else {
														currentIndentation = ((currentIndentation / this.tabLength) + 1) * this.tabLength;
													}
												}
												break;
											default :
												currentIndentation ++;
										}
										currentCharacter = this.scanner.getNextChar();
										nextCharacterStart = this.scanner.currentPosition;
									}
									if (currentCharacter == '\r' || currentCharacter == '\n') {
										nextCharacterStart = previousStartPosition;
									}
								}
								if (formatComment) {
									int previousStartTemp = previousStart;
									int nextCharacterStartTemp = nextCharacterStart;
									while(currentCharacter != -1 && currentCharacter != '\r' && currentCharacter != '\n' && ScannerHelper.isWhitespace((char) currentCharacter)) {
										previousStart = nextCharacterStart;
										currentCharacter = this.scanner.getNextChar();
										nextCharacterStart = this.scanner.currentPosition;
									}
									if (currentCharacter == '*') {
										addSpace = true;
									} else {
										previousStart = previousStartTemp;
										nextCharacterStart = nextCharacterStartTemp;
									}
									this.scanner.currentPosition = nextCharacterStart;
								}
							}
							String replacement;
							if (indentComment) {
								this.tempBuffer.setLength(0);
								this.tempBuffer.append(this.lineSeparator);
								if (this.indentationLevel > 0) {
									printIndentationIfNecessary(this.tempBuffer);
								}
								if (addSpace) {
									this.tempBuffer.append(' ');
								}
								replacement = this.tempBuffer.toString();
							} else {
								replacement = addSpace ? this.lineSeparatorAndSpace : this.lineSeparator;
							}
							addReplaceEdit(start, previousStart - 1, replacement);
						} else {
							this.column += (nextCharacterStart - previousStart);
						}
				}
				previousStart = nextCharacterStart;
				this.scanner.currentPosition = nextCharacterStart;
			}
		} finally {
			this.indentationLevel = currentIndentationLevel;
		}
		this.lastNumberOfNewLines = 0;
		this.needSpace = false;
		this.scanner.resetTo(currentTokenEndPosition, this.scannerEndPosition - 1);
	}

	private boolean printBlockComment(int currentTokenStartPosition, int currentTokenEndPosition) {

		// Compute indentation
		int maxColumn = this.formatter.preferences.comment_line_length + 1;
		int indentLevel = this.indentationLevel;
		int indentations = this.numberOfIndentations;
		switch (this.tabChar) {
			case DefaultCodeFormatterOptions.TAB:
				switch (this.tabLength) {
					case 0:
						this.indentationLevel = 0;
						this.column = 1;
						this.numberOfIndentations = 0;
						break;
					case 1:
						this.indentationLevel = this.column - 1;
						this.numberOfIndentations = this.indentationLevel;
						break;
					default:
						this.indentationLevel = (this.column / this.tabLength) * this.tabLength;
						this.column = this.indentationLevel + 1;
						this.numberOfIndentations = this.indentationLevel / this.tabLength;
				}
				break;
			case DefaultCodeFormatterOptions.MIXED:
				if (this.tabLength == 0) {
					this.indentationLevel = 0;
					this.column = 1;
					this.numberOfIndentations = 0;
				} else {
					this.indentationLevel = this.column - 1;
					this.numberOfIndentations = this.indentationLevel / this.tabLength;
				}
				break;
			case DefaultCodeFormatterOptions.SPACE:
				if (this.indentationSize == 0) {
					this.indentationLevel = 0;
					this.column = 1;
					this.numberOfIndentations = 0;
				} else {
					this.indentationLevel = this.column - 1;
				}
				break;
		}

		// Consume the comment prefix
		this.blockCommentBuffer.setLength(0);
		this.scanner.getNextChar();
		this.scanner.getNextChar();
		this.column += 2;
		this.scanner.skipComments = true;
		this.blockCommentTokensBuffer.setLength(0);
		int editStart = this.scanner.currentPosition;
		int editEnd = -1;

		// Consume text token per token
		int previousToken = -1;
		boolean newLine = false;
		boolean multiLines = false;
		boolean hasMultiLines = false;
		boolean hasTokens = false;
		boolean bufferHasTokens = false;
		boolean bufferHasNewLine = false;
		boolean lineHasTokens = false;
		int hasTextOnFirstLine = 0;
		boolean firstWord = true;
		boolean clearBlankLines = this.formatter.preferences.comment_clear_blank_lines_in_block_comment;
		boolean joinLines = this.formatter.preferences.join_lines_in_comments;
		boolean newLinesAtBoundaries = this.formatter.preferences.comment_new_lines_at_block_boundaries;
		int scannerLine = Util.getLineNumber(this.scanner.currentPosition, this.lineEnds, 0, this.maxLines);
		int firstLine = scannerLine;
		int lineNumber = scannerLine;
		int lastTextLine = -1;
		while (!this.scanner.atEnd()) {

			// Consume token
			int token;
			try {
				token = this.scanner.getNextToken();
			} catch (InvalidInputException iie) {
	    		token = consumeInvalidToken(currentTokenEndPosition-1);
				newLine = false;
			}

			// Look at specific tokens
    		boolean insertSpace = (previousToken == TerminalTokens.TokenNameWHITESPACE) && (!firstWord || !hasTokens);
    		boolean isTokenStar = false;
			switch (token) {
				case TerminalTokens.TokenNameWHITESPACE:
					if (this.blockCommentTokensBuffer.length() > 0) {
						if (hasTextOnFirstLine == 1 && multiLines) {
							printBlockCommentHeaderLine(this.blockCommentBuffer);
							hasTextOnFirstLine = -1;
						}
						this.blockCommentBuffer.append(this.blockCommentTokensBuffer);
						this.column += this.blockCommentTokensBuffer.length();
						this.blockCommentTokensBuffer.setLength(0);
						bufferHasTokens = true;
						bufferHasNewLine = false;
					}
					if (previousToken == -1) {
						// do not remember the first whitespace
						previousToken = SKIP_FIRST_WHITESPACE_TOKEN;
					} else {
						previousToken = token;
					}
					lineNumber = Util.getLineNumber(this.scanner.currentPosition, this.lineEnds, scannerLine>1 ? scannerLine-2 : 0, this.maxLines);
					if (lineNumber > scannerLine) {
						hasMultiLines = true;
						newLine = true;
					}
					scannerLine = lineNumber;
					continue;
				case TerminalTokens.TokenNameMULTIPLY:
					isTokenStar = true;
					lineNumber = Util.getLineNumber(this.scanner.currentPosition, this.lineEnds, scannerLine>1 ? scannerLine-2 : 0, this.maxLines);
					if (lineNumber == firstLine && previousToken == SKIP_FIRST_WHITESPACE_TOKEN) {
						this.blockCommentBuffer.append(' ');
					}
					previousToken = token;
					if (this.scanner.currentCharacter == '/') {
						editEnd = this.scanner.startPosition - 1;
						// Add remaining buffered tokens
						if (this.blockCommentTokensBuffer.length() > 0) {
							this.blockCommentBuffer.append(this.blockCommentTokensBuffer);
							this.column += this.blockCommentTokensBuffer.length();
						}
						// end of comment
						if (newLinesAtBoundaries) {
							if (multiLines || hasMultiLines) {
						    	this.blockCommentBuffer.append(this.lineSeparator);
						    	this.column = 1;
						    	printIndentationIfNecessary(this.blockCommentBuffer);
							}
						}
						this.blockCommentBuffer.append(' ');
						this.column += BLOCK_FOOTER_LENGTH + 1;
				    	this.scanner.getNextChar(); // reach the end of scanner
				    	continue;
					}
					if (newLine) {
						scannerLine = lineNumber;
						newLine = false;
						continue;
					}
					break;
				case TerminalTokens.TokenNameMULTIPLY_EQUAL:
					if (newLine) {
						this.scanner.resetTo(this.scanner.startPosition, currentTokenEndPosition-1);
						this.scanner.getNextChar(); // consume the multiply
						previousToken = TerminalTokens.TokenNameMULTIPLY;
						scannerLine = Util.getLineNumber(this.scanner.currentPosition, this.lineEnds, scannerLine>1 ? scannerLine-2 : 0, this.maxLines);
						continue;
					}
					break;
				case TerminalTokens.TokenNameMINUS:
				case TerminalTokens.TokenNameMINUS_MINUS:
					if (previousToken == -1) {
						// Do not format comment starting with /*-
						// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=230944
						this.indentationLevel = indentLevel;
						this.numberOfIndentations = indentations;
						this.lastNumberOfNewLines = 0;
						this.needSpace = false;
						this.scanner.skipComments = false;
						this.scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition - 1);
						return false;
					}
					break;
				default:
					// do nothing
					break;
			}

			// Look at gap and insert corresponding lines if necessary
			int linesGap;
			int max;
			lineNumber = Util.getLineNumber(this.scanner.currentPosition, this.lineEnds, scannerLine>1 ? scannerLine-2 : 0, this.maxLines);
			if (lastTextLine == -1) {
				linesGap = newLinesAtBoundaries ? lineNumber - firstLine : 0;
				max = 0;
			} else {
				linesGap = lineNumber - lastTextLine;
				if (token == TerminalTokens.TokenNameAT && linesGap ==1) {
					// insert one blank line before root tags
					linesGap = 2;
				}
				max = joinLines && lineHasTokens ? 1 : 0;
			}
			if (linesGap > max) {
				if (clearBlankLines) {
					// TODO (frederic) see if there's a bug for the unremoved blank line for root tags
					 if (token == TerminalTokens.TokenNameAT) {
						 linesGap = 1;
					 } else {
						linesGap = (max==0 || !joinLines) ? 1 : 0;
					 }
				}
				for (int i=0; i<linesGap; i++) {
					// Add remaining buffered tokens
					if (this.blockCommentTokensBuffer.length() > 0) {
						if (hasTextOnFirstLine == 1) {
							printBlockCommentHeaderLine(this.blockCommentBuffer);
							hasTextOnFirstLine = -1;
						}
						this.blockCommentBuffer.append(this.blockCommentTokensBuffer);
						this.blockCommentTokensBuffer.setLength(0);
						bufferHasTokens = true;
					}
			    	this.blockCommentBuffer.append(this.lineSeparator);
			    	this.column = 1;
			    	printIndentationIfNecessary(this.blockCommentBuffer);
		    		this.blockCommentBuffer.append(BLOCK_LINE_PREFIX);
		    		this.column += BLOCK_LINE_PREFIX_LENGTH;
		    		firstWord = true;
					multiLines = true;
					bufferHasNewLine = true;
				}
				insertSpace = insertSpace && linesGap == 0;
			}
    		if (newLine) lineHasTokens = false;

			// Increment column
			int tokenStart = this.scanner.getCurrentTokenStartPosition();
    		int tokenLength = (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - tokenStart;
    		hasTokens = true;
    		if (!isTokenStar) lineHasTokens = true;
    		if (hasTextOnFirstLine == 0 && !isTokenStar) {
    			if (firstLine == lineNumber) {
	    			hasTextOnFirstLine = 1;
	    			this.column++; // include first space
	    		} else {
	    			hasTextOnFirstLine = -1;
	    		}
    		}
    		int lastColumn = this.column + this.blockCommentTokensBuffer.length() + tokenLength;
    		if (insertSpace) lastColumn++;

    		// Append next token inserting a new line if max line is reached
			if (lineHasTokens && !firstWord && lastColumn > maxColumn) {
		    	String tokensString = this.blockCommentTokensBuffer.toString().trim();
		    	int tokensStringLength = tokensString.length();
				// not enough space on the line
				if (hasTextOnFirstLine == 1) {
					printBlockCommentHeaderLine(this.blockCommentBuffer);
				}
				if ((this.indentationLevel+tokensStringLength+tokenLength) > maxColumn) {
					// there won't be enough room even if we break the line before the buffered tokens
					// So add the buffered tokens now
					this.blockCommentBuffer.append(this.blockCommentTokensBuffer);
					this.column += this.blockCommentTokensBuffer.length();
					this.blockCommentTokensBuffer.setLength(0);
					bufferHasNewLine = false;
					bufferHasTokens = true;
				}
				if (bufferHasTokens && !bufferHasNewLine) {
			    	this.blockCommentBuffer.append(this.lineSeparator);
			    	this.column = 1;
			    	printIndentationIfNecessary(this.blockCommentBuffer);
		    		this.blockCommentBuffer.append(BLOCK_LINE_PREFIX);
			    	this.column += BLOCK_LINE_PREFIX_LENGTH;
				}
		    	if (this.blockCommentTokensBuffer.length() > 0) {
					this.blockCommentBuffer.append(tokensString);
					this.column += tokensStringLength;
					this.blockCommentTokensBuffer.setLength(0);
		    	}
				this.blockCommentBuffer.append(this.scanner.source, tokenStart, tokenLength);
				bufferHasTokens = true;
				bufferHasNewLine = false;
				this.column += tokenLength;
				multiLines = true;
				hasTextOnFirstLine = -1;
			} else {
				// append token to the line
				if (insertSpace)  {
					this.blockCommentTokensBuffer.append(' ');
				}
				this.blockCommentTokensBuffer.append(this.scanner.source, tokenStart, tokenLength);
			}
			previousToken = token;
			newLine = false;
    		firstWord = false;
			scannerLine = lineNumber;
			lastTextLine = lineNumber;
		}

		// Replace block comment text
		if (this.nlsTagCounter == 0 || !multiLines) {
			if (hasTokens || multiLines) {
				StringBuffer replacement;
				if (hasTextOnFirstLine == 1) {
					this.blockCommentTokensBuffer.setLength(0);
					replacement = this.blockCommentTokensBuffer;
					if ((hasMultiLines || multiLines)) {
						int col = this.column;
						replacement.append(this.lineSeparator);
						this.column = 1;
						printIndentationIfNecessary(replacement);
						replacement.append(BLOCK_LINE_PREFIX);
				    	this.column = col;
					} else if (this.blockCommentBuffer.length()==0 || this.blockCommentBuffer.charAt(0)!=' ') {
						replacement.append(' ');
					}
					replacement.append(this.blockCommentBuffer);
				} else {
					replacement = this.blockCommentBuffer;
				}
				addReplaceEdit(editStart, editEnd, replacement.toString());
			}
		}

		// Reset
		this.indentationLevel = indentLevel;
		this.numberOfIndentations = indentations;
		this.lastNumberOfNewLines = 0;
		this.needSpace = false;
		this.scanner.resetTo(currentTokenEndPosition, this.scannerEndPosition - 1);
		this.scanner.skipComments = false;
		return true;
	}

	private void printBlockCommentHeaderLine(StringBuffer buffer) {
		if (!this.formatter.preferences.comment_new_lines_at_block_boundaries) {
			buffer.insert(0, ' ');
			this.column++;
		}
	    else if (buffer.length() == 0) {
	    	buffer.append(this.lineSeparator);
	    	this.column = 1;
	    	printIndentationIfNecessary(buffer);
	    	buffer.append(BLOCK_LINE_PREFIX);
	    	this.column += BLOCK_LINE_PREFIX_LENGTH;
	    } else {
	    	this.tempBuffer.setLength(0);
	    	this.tempBuffer.append(this.lineSeparator);
	    	this.column = 1;
			printIndentationIfNecessary(this.tempBuffer);
	    	this.tempBuffer.append(BLOCK_LINE_PREFIX);
	    	this.column += BLOCK_LINE_PREFIX_LENGTH;
	    	buffer.insert(0, this.tempBuffer.toString());
	    }
    }

	public void printEndOfCompilationUnit() {
		try {
			// if we have a space between two tokens we ensure it will be dumped in the formatted string
			int currentTokenStartPosition = this.scanner.currentPosition;
			boolean hasComment = false;
			boolean hasLineComment = false;
			boolean hasWhitespace = false;
			int count = 0;
			while (true) {
				this.currentToken = this.scanner.getNextToken();
				switch(this.currentToken) {
					case TerminalTokens.TokenNameWHITESPACE :
						char[] whiteSpaces = this.scanner.getCurrentTokenSource();
						count = 0;
						for (int i = 0, max = whiteSpaces.length; i < max; i++) {
							switch(whiteSpaces[i]) {
								case '\r' :
									if ((i + 1) < max) {
										if (whiteSpaces[i + 1] == '\n') {
											i++;
										}
									}
									count++;
									break;
								case '\n' :
									count++;
							}
						}
						if (count == 0) {
							hasWhitespace = true;
							addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
						} else if (hasLineComment) {
							preserveEmptyLines(count, this.scanner.getCurrentTokenStartPosition());
							addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
						} else if (hasComment) {
							if (count == 1) {
								this.printNewLine(this.scanner.getCurrentTokenStartPosition());
							} else {
								preserveEmptyLines(count - 1, this.scanner.getCurrentTokenStartPosition());
							}
							addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
						} else {
							addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
						}
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameCOMMENT_LINE :
						if (count >= 1) {
							if (count > 1) {
								preserveEmptyLines(count - 1, this.scanner.getCurrentTokenStartPosition());
							} else if (count == 1) {
								printNewLine(this.scanner.getCurrentTokenStartPosition());
							}
						} else if (hasWhitespace) {
							space();
						}
						hasWhitespace = false;
						printLineComment();
						currentTokenStartPosition = this.scanner.currentPosition;
						hasLineComment = true;
						count = 0;
						break;
					case TerminalTokens.TokenNameCOMMENT_BLOCK :
						if (count >= 1) {
							if (count > 1) {
								preserveEmptyLines(count - 1, this.scanner.getCurrentTokenStartPosition());
							} else if (count == 1) {
								printNewLine(this.scanner.getCurrentTokenStartPosition());
							}
						} else if (hasWhitespace) {
							space();
						}
						hasWhitespace = false;
						printBlockComment(false);
						currentTokenStartPosition = this.scanner.currentPosition;
						hasLineComment = false;
						hasComment = true;
						count = 0;
						break;
					case TerminalTokens.TokenNameCOMMENT_JAVADOC :
						if (count >= 1) {
							if (count > 1) {
								preserveEmptyLines(count - 1, this.scanner.startPosition);
							} else if (count == 1) {
								printNewLine(this.scanner.startPosition);
							}
						} else if (hasWhitespace) {
							space();
						}
						hasWhitespace = false;
						if (includesJavadocComments()) {
							printJavadocComment(this.scanner.startPosition, this.scanner.currentPosition);
						} else {
							printBlockComment(true);
						}
						printNewLine();
						currentTokenStartPosition = this.scanner.currentPosition;
						hasLineComment = false;
						hasComment = true;
						count = 0;
						break;
					case TerminalTokens.TokenNameSEMICOLON :
						print(this.scanner.currentPosition - this.scanner.startPosition, this.formatter.preferences.insert_space_before_semicolon);
						break;
					case TerminalTokens.TokenNameEOF :
						if (count >= 1 || this.formatter.preferences.insert_new_line_at_end_of_file_if_missing) {
							this.printNewLine(this.scannerEndPosition);
						}
						return;
					default :
						// step back one token
						this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
						return;
				}
			}
		} catch (InvalidInputException e) {
			throw new AbortFormatting(e);
		}
	}

	/*
	 * prints a code snippet
	 */
	private void printCodeSnippet(int startPosition, int endPosition, int linesGap) {
		String snippet = new String(this.scanner.source, startPosition, endPosition - startPosition + 1);
	
		// 1 - strip content prefix (@see JavaDocRegion#preprocessCodeSnippet)
		int firstLine = Util.getLineNumber(startPosition, this.lineEnds, 0, this.maxLines) - 1;
		int lastLine = Util.getLineNumber(endPosition, this.lineEnds, firstLine>1 ? firstLine-2 : 0, this.maxLines) - 1;
		this.codeSnippetBuffer.setLength(0);
		if (firstLine == lastLine && linesGap == 0) {
			this.codeSnippetBuffer.append(snippet);
		} else {
			boolean hasCharsAfterStar = false;
			if (linesGap == 0) {
				this.codeSnippetBuffer.append(this.scanner.source, startPosition, this.lineEnds[firstLine]+1-startPosition);
				firstLine++;
			}
			int initialLength = this.codeSnippetBuffer.length();
			for (int currentLine=firstLine; currentLine<=lastLine; currentLine++) {
				this.scanner.resetTo(this.lineEnds[currentLine-1]+1, this.lineEnds[currentLine]);
				int lineStart = this.scanner.currentPosition;
				boolean hasStar = false;
				loop: while (!this.scanner.atEnd()) {
					char ch = (char) this.scanner.getNextChar();
					switch (ch) {
						case ' ':
						case '\t' :
						case '\u000c' :
							break;
						case '\r' :
						case '\n' :
							break loop;
						case '*':
							hasStar = true;
							break loop;
						default:
							if (ScannerHelper.isWhitespace(ch)) {
								break;
							}
							break loop;
					}
				}
				if (hasStar) {
					lineStart = this.scanner.currentPosition;
					if (!hasCharsAfterStar && !this.scanner.atEnd()) {
						char ch = (char) this.scanner.getNextChar();
						boolean atEnd = this.scanner.atEnd();
						switch (ch) {
							case ' ':
							case '\t' :
							case '\u000c' :
								break;
							case '\r' :
							case '\n' :
								atEnd = true;
								break;
							default:
								if (!ScannerHelper.isWhitespace(ch)) {
									if (hasStar) {
										// A non whitespace character is just after the star
										// then we need to restart from the beginning without
										// consuming the space after the star
										hasCharsAfterStar = true;
										currentLine = firstLine-1;
										this.codeSnippetBuffer.setLength(initialLength);
										continue;
									}
								}
								break;
						}
						if (!hasCharsAfterStar && !atEnd) {
							// Until then, there's always a whitespace after each star
							// of the comment, hence we need to consume it as it will
							// be rewritten while reindenting the snippet lines
							lineStart = this.scanner.currentPosition;
						}
					}
				}
				int end = currentLine == lastLine ? endPosition : this.lineEnds[currentLine];
				this.codeSnippetBuffer.append(this.scanner.source, lineStart, end+1-lineStart);
			}
		}
	
		// 2 - convert HTML to Java (@see JavaDocRegion#convertHtml2Java)
		HTMLEntity2JavaReader reader= new HTMLEntity2JavaReader(new StringReader(this.codeSnippetBuffer.toString()));
		char[] buf= new char[this.codeSnippetBuffer.length()]; // html2text never gets longer, only shorter!
		String convertedSnippet;
		try {
			int read= reader.read(buf);
			convertedSnippet = new String(buf, 0, read);
		} catch (IOException e) {
			// should not happen
			CommentFormatterUtil.log(e);
			return;
		}
	
		// 3 - format snippet (@see JavaDocRegion#formatCodeSnippet)
		// include comments in case of line comments are present in the snippet
		String formattedSnippet = convertedSnippet;
		Map options = this.formatter.preferences.getMap();
		if (this.scanner.sourceLevel > ClassFileConstants.JDK1_3) {
			options.put(JavaCore.COMPILER_SOURCE, CompilerOptions.versionFromJdkLevel(this.scanner.sourceLevel));
		}
		TextEdit edit= CommentFormatterUtil.format2(CodeFormatter.K_UNKNOWN | CodeFormatter.F_INCLUDE_COMMENTS, convertedSnippet, 0, this.lineSeparator, options);
		if (edit == null) {
			// 3.a - not a valid code to format, keep initial buffer
			formattedSnippet = this.codeSnippetBuffer.toString();
		} else {
			// 3.b - valid code formatted
			// 3.b.i - get the result
			formattedSnippet = CommentFormatterUtil.evaluateFormatterEdit(convertedSnippet, edit, null);
	
			// 3.b.ii- convert back to HTML (@see JavaDocRegion#convertJava2Html)
			Java2HTMLEntityReader javaReader= new Java2HTMLEntityReader(new StringReader(formattedSnippet));
			buf= new char[256];
			this.codeSnippetBuffer.setLength(0);
			int l;
			try {
				do {
					l= javaReader.read(buf);
					if (l != -1)
						this.codeSnippetBuffer.append(buf, 0, l);
				} while (l > 0);
				formattedSnippet = this.codeSnippetBuffer.toString();
			} catch (IOException e) {
				// should not happen
				CommentFormatterUtil.log(e);
				return;
			}
		}
	
		// 4 - add the content prefix (@see JavaDocRegion#postprocessCodeSnippet)
		this.codeSnippetBuffer.setLength(0);
		ILineTracker tracker = new DefaultLineTracker();
		this.column = 1;
		printIndentationIfNecessary(this.codeSnippetBuffer); // append indentation
		this.codeSnippetBuffer.append(BLOCK_LINE_PREFIX);
		String linePrefix = this.codeSnippetBuffer.toString();
		this.codeSnippetBuffer.setLength(0);
		String replacement = formattedSnippet;
		tracker.set(formattedSnippet);
		int numberOfLines = tracker.getNumberOfLines();
		if (numberOfLines > 1) {
			int lastLineOffset = -1;
			for (int i=0; i<numberOfLines-1; i++) {
				if (i>0) this.codeSnippetBuffer.append(linePrefix);
				try {
					lastLineOffset = tracker.getLineOffset(i+1);
					this.codeSnippetBuffer.append(formattedSnippet.substring(tracker.getLineOffset(i), lastLineOffset));
				} catch (BadLocationException e) {
					// should not happen
					CommentFormatterUtil.log(e);
					return;
				}
			}
			this.codeSnippetBuffer.append(linePrefix);
			this.codeSnippetBuffer.append(formattedSnippet.substring(lastLineOffset));
			replacement = this.codeSnippetBuffer.toString();
		}
	
		// 5 - replace old text with the formatted snippet
		addReplaceEdit(startPosition, endPosition, replacement);
	}

	void printComment() {
		printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT, PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION);
	}

	void printComment(int emptyLinesRules) {
		printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT, emptyLinesRules);
	}

	void printComment(int kind, int trailing) {
		printComment(kind, trailing, PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION);
	}

	/*
	 * Main method to print and format comments (javadoc, block and single line comments)
	 */
	void printComment(int kind, int trailing, int emptyLinesRules) {
		final boolean rejectLineComment = kind  == CodeFormatter.K_MULTI_LINE_COMMENT || kind == CodeFormatter.K_JAVA_DOC;
		final boolean rejectBlockComment = kind  == CodeFormatter.K_SINGLE_LINE_COMMENT || kind  == CodeFormatter.K_JAVA_DOC;
		final boolean rejectJavadocComment = kind  == CodeFormatter.K_SINGLE_LINE_COMMENT || kind  == CodeFormatter.K_MULTI_LINE_COMMENT;
		try {
			// if we have a space between two tokens we ensure it will be dumped in the formatted string
			int currentTokenStartPosition = this.scanner.currentPosition;
			boolean hasComment = false;
			boolean hasLineComment = false;
			boolean hasWhitespaces = false;
			int lines = 0;
			while ((this.currentToken = this.scanner.getNextToken()) != TerminalTokens.TokenNameEOF) {
				int foundTaskCount = this.scanner.foundTaskCount;
				int tokenStartPosition = this.scanner.getCurrentTokenStartPosition();
				switch(this.currentToken) {
					case TerminalTokens.TokenNameWHITESPACE :
						char[] whiteSpaces = this.scanner.getCurrentTokenSource();
						int whitespacesEndPosition = this.scanner.getCurrentTokenEndPosition();
						lines = 0;
						for (int i = 0, max = whiteSpaces.length; i < max; i++) {
							switch(whiteSpaces[i]) {
								case '\r' :
									if ((i + 1) < max) {
										if (whiteSpaces[i + 1] == '\n') {
											i++;
										}
									}
									lines++;
									break;
								case '\n' :
									lines++;
							}
						}
						// If following token is a line comment on the same line or the line just after,
						// then it might be not really formatted as a trailing comment
						boolean realTrailing = trailing > NO_TRAILING_COMMENT;
						if (realTrailing && this.scanner.currentCharacter == '/' && (lines == 0 || (lines == 1 && !hasLineComment && trailing == IMPORT_TRAILING_COMMENT))) {
							// sometimes changing the trailing may not be the best idea
							// for complex trailing comment, it's basically a good idea
							boolean canChangeTrailing = (trailing & COMPLEX_TRAILING_COMMENT) != 0;
							// for basic trailing comment preceded by a line comment, then it depends on the comments relative position
							// when following comment column (after having been rounded) is below the preceding one,
							// then it becomes not a good idea to change the trailing flag
							if (trailing == BASIC_TRAILING_COMMENT && hasLineComment) {
								int currentCommentIndentation = getCurrentIndentation(whiteSpaces, 0);
								int relativeIndentation = currentCommentIndentation - this.lastLineComment.currentIndentation;
								if (this.tabLength == 0) {
									canChangeTrailing = relativeIndentation == 0;
								} else {
									canChangeTrailing = relativeIndentation > -this.tabLength;
								}
							}
							// if the trailing can be change, then look at the following tokens
							if (canChangeTrailing) {
								int currentPosition = this.scanner.currentPosition;
								if (this.scanner.getNextToken() == TerminalTokens.TokenNameCOMMENT_LINE) {
									realTrailing = !hasLineComment;
									switch (this.scanner.getNextToken()) {
										case TerminalTokens.TokenNameCOMMENT_LINE:
											// at least two contiguous line comments
											// the formatter should not consider comments as trailing ones
											realTrailing = false;
											break;
										case TerminalTokens.TokenNameWHITESPACE:
											if (this.scanner.getNextToken() == TerminalTokens.TokenNameCOMMENT_LINE) {
												// at least two contiguous line comments
												// the formatter should not consider comments as trailing ones
												realTrailing = false;
											}
											break;
									}
								}
								this.scanner.resetTo(currentPosition, this.scanner.eofPosition - 1);
							}
						}
						// Look whether comments line may be contiguous or not
						// Note that when preceding token is a comment line, then only one line
						// is enough to have an empty line as the line end is included in the comment line...
						// If comments are contiguous, store the white spaces to be able to compute the current comment indentation
						if (lines > 1 || (lines == 1 && hasLineComment)) {
							this.lastLineComment.contiguous = false;
						}
						this.lastLineComment.leadingSpaces = whiteSpaces;
						this.lastLineComment.lines = lines;
						// Strategy to consume spaces and eventually leave at this stage
						// depends on the fact that a trailing comment is expected or not
						if (realTrailing) {
							// if a line comment is consumed, no other comment can be on the same line after
							if (hasLineComment) {
								if (lines >= 1) {
									currentTokenStartPosition = tokenStartPosition;
									preserveEmptyLines(lines, currentTokenStartPosition);
									addDeleteEdit(currentTokenStartPosition, whitespacesEndPosition);
									this.scanner.resetTo(this.scanner.currentPosition, this.scannerEndPosition - 1);
									return;
								}
								this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
								return;
							} 
							// if one or several new lines are consumed, following comments cannot be considered as trailing ones
							if (lines >= 1) {
								if (hasComment) {
									this.printNewLine(tokenStartPosition);
								}
								this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
								return;
							}
							// delete consumed white spaces
							hasWhitespaces = true;
							currentTokenStartPosition = this.scanner.currentPosition;
							addDeleteEdit(tokenStartPosition, whitespacesEndPosition);
						} else {
							if (lines == 0) {
								hasWhitespaces = true;
								if (hasLineComment && emptyLinesRules != PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION) {
									addReplaceEdit(tokenStartPosition, whitespacesEndPosition, getPreserveEmptyLines(0, emptyLinesRules));
								} else {
									addDeleteEdit(tokenStartPosition, whitespacesEndPosition);
								}
							} else if (hasLineComment) {
								useAlignmentBreakIndentation(emptyLinesRules);
								currentTokenStartPosition = tokenStartPosition;
								preserveEmptyLines(lines, currentTokenStartPosition);
								addDeleteEdit(currentTokenStartPosition, whitespacesEndPosition);
							} else if (hasComment) {
								useAlignmentBreakIndentation(emptyLinesRules);
								if (lines == 1) {
									this.printNewLine(tokenStartPosition);
								} else {
									preserveEmptyLines(lines - 1, tokenStartPosition);
								}
								addDeleteEdit(tokenStartPosition, whitespacesEndPosition);
							} else if (lines != 0 && (!this.formatter.preferences.join_wrapped_lines || this.formatter.preferences.number_of_empty_lines_to_preserve != 0 || this.blank_lines_between_import_groups > 0)) {
								addReplaceEdit(tokenStartPosition, whitespacesEndPosition, getPreserveEmptyLines(lines-1, emptyLinesRules));
							} else {
								useAlignmentBreakIndentation(emptyLinesRules);
								addDeleteEdit(tokenStartPosition, whitespacesEndPosition);
							}
						}
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameCOMMENT_LINE :
						if (this.useTags && this.editsEnabled) {
							boolean turnOff = false;
							if (foundTaskCount > 0) {
								setEditsEnabled(foundTaskCount);
								turnOff = true;
							} else if (this.tagsKind == this.currentToken
								&& CharOperation.fragmentEquals(this.disablingTag, this.scanner.source, tokenStartPosition, true)) {
    							this.editsEnabled = false;
								turnOff = true;
					    	}
							if (turnOff) {
								if (!this.editsEnabled && this.editsIndex > 1) {
									OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex-1];
									if (this.scanner.startPosition == currentEdit.offset+currentEdit.length) {
										printNewLinesBeforeDisablingComment();
									}
								}
							}
						}
						if (rejectLineComment) break;
						if (lines >= 1) {
							if (lines > 1) {
								preserveEmptyLines(lines - 1, this.scanner.getCurrentTokenStartPosition());
							} else if (lines == 1) {
								printNewLine(this.scanner.getCurrentTokenStartPosition());
							}
						} else if (hasWhitespaces) {
							space();
						}
						hasWhitespaces = false;
						printLineComment();
						currentTokenStartPosition = this.scanner.currentPosition;
						hasLineComment = true;
						lines = 0;
						if (this.useTags && !this.editsEnabled) {
							if (foundTaskCount > 0) {
								setEditsEnabled(foundTaskCount);
							} else if (this.tagsKind == this.currentToken) {
	    						this.editsEnabled = CharOperation.fragmentEquals(this.enablingTag, this.scanner.source, tokenStartPosition, true);
					    	}
						}
						break;
					case TerminalTokens.TokenNameCOMMENT_BLOCK :
						if (this.useTags && this.editsEnabled) {
							boolean turnOff = false;
							if (foundTaskCount > 0) {
								setEditsEnabled(foundTaskCount);
								turnOff = true;
							} else if (this.tagsKind == this.currentToken
								&& CharOperation.fragmentEquals(this.disablingTag, this.scanner.source, tokenStartPosition, true)) {
    							this.editsEnabled = false;
								turnOff = true;
					    	}
							if (turnOff) {
								if (!this.editsEnabled && this.editsIndex > 1) {
									OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex-1];
									if (this.scanner.startPosition == currentEdit.offset+currentEdit.length) {
										printNewLinesBeforeDisablingComment();
									}
								}
							}
						}
						if (trailing > NO_TRAILING_COMMENT && lines >= 1) {
							// a block comment on next line means that there's no trailing comment
							this.scanner.resetTo(this.scanner.getCurrentTokenStartPosition(), this.scannerEndPosition - 1);
							return;
						}
						this.lastLineComment.contiguous = false;
						if (rejectBlockComment) break;
						if (lines >= 1) {
							if (lines > 1) {
								preserveEmptyLines(lines - 1, this.scanner.getCurrentTokenStartPosition());
							} else if (lines == 1) {
								printNewLine(this.scanner.getCurrentTokenStartPosition());
							}
						} else if (hasWhitespaces) {
							space();
						}
						hasWhitespaces = false;
						printBlockComment(false);
						currentTokenStartPosition = this.scanner.currentPosition;
						hasLineComment = false;
						hasComment = true;
						lines = 0;
						if (this.useTags && !this.editsEnabled) {
							if (foundTaskCount > 0) {
								setEditsEnabled(foundTaskCount);
							} else if (this.tagsKind == this.currentToken) {
	    						this.editsEnabled = CharOperation.fragmentEquals(this.enablingTag, this.scanner.source, tokenStartPosition, true);
					    	}
						}
						break;
					case TerminalTokens.TokenNameCOMMENT_JAVADOC :
						if (this.useTags && this.editsEnabled && foundTaskCount > 0) {
							setEditsEnabled(foundTaskCount);
							if (!this.editsEnabled && this.editsIndex > 1) {
								OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex-1];
								if (this.scanner.startPosition == currentEdit.offset+currentEdit.length) {
									printNewLinesBeforeDisablingComment();
								}
							}
						}
						if (trailing > NO_TRAILING_COMMENT) {
							// a javadoc comment should not be considered as a trailing comment
							this.scanner.resetTo(this.scanner.getCurrentTokenStartPosition(), this.scannerEndPosition - 1);
							return;
						}
						this.lastLineComment.contiguous = false;
						if (rejectJavadocComment) break;
						if (lines >= 1) {
							if (lines > 1) {
								preserveEmptyLines(lines - 1, this.scanner.getCurrentTokenStartPosition());
							} else if (lines == 1) {
								printNewLine(this.scanner.getCurrentTokenStartPosition());
							}
						} else if (hasWhitespaces) {
							space();
						}
						hasWhitespaces = false;
						if (includesJavadocComments()) {
							printJavadocComment(this.scanner.startPosition, this.scanner.currentPosition);
						} else {
							printBlockComment(true);
						}
						if (this.useTags && !this.editsEnabled && foundTaskCount > 0) {
							setEditsEnabled(foundTaskCount);
						}
						printNewLine();
						currentTokenStartPosition = this.scanner.currentPosition;
						hasLineComment = false;
						hasComment = true;
						lines = 0;
						break;
					default :
						this.lastLineComment.contiguous = false;
						// step back one token
						this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
						return;
				}
			}
		} catch (InvalidInputException e) {
			throw new AbortFormatting(e);
		}
	}

	void printComment(int kind, String source, int start, int end, int level) {

		// Set scanner
		resetScanner(source.toCharArray());
		this.scanner.resetTo(start, end);
		// Put back 3.4RC2 code => comment following line  as it has an impact on Linux tests
		// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=234336
		// TODO (frederic) Need more investigations and a better fix in
		// isAdaptableRegion(int) and adaptRegions()
		// this.scannerEndPosition = end;

		// Set indentation level
	    this.numberOfIndentations = level;
	    this.indentationLevel = level * this.indentationSize;
	    this.column = this.indentationLevel + 1;

	    // Print corresponding comment
	    switch (kind) {
	    	case CodeFormatter.K_SINGLE_LINE_COMMENT:
			    printComment(kind, NO_TRAILING_COMMENT);
	    		break;
	    	case CodeFormatter.K_MULTI_LINE_COMMENT:
			    printComment(kind, NO_TRAILING_COMMENT);
	    		break;
	    	case CodeFormatter.K_JAVA_DOC:
	    		printJavadocComment(start, end);
	    		break;
	    }
    }

	private void printLineComment() {
    	int currentTokenStartPosition = this.scanner.getCurrentTokenStartPosition();
    	int currentTokenEndPosition = this.scanner.getCurrentTokenEndPosition() + 1;
    	boolean includesLineComments = includesLineComments();
    	boolean isNlsTag = false;
    	if (CharOperation.indexOf(Scanner.TAG_PREFIX, this.scanner.source, true, currentTokenStartPosition, currentTokenEndPosition) != -1) {
    		this.nlsTagCounter = 0;
    		isNlsTag = true;
    	}
    	this.scanner.resetTo(currentTokenStartPosition, currentTokenEndPosition - 1);
    	int currentCharacter;
    	int start = currentTokenStartPosition;
    	int nextCharacterStart = currentTokenStartPosition;

    	// Print comment line indentation
    	int commentIndentationLevel;
   		boolean onFirstColumn = isOnFirstColumn(start);
    	if (this.indentationLevel == 0) {
    		commentIndentationLevel = this.column - 1;
    	} else {
			if (onFirstColumn &&
					((includesLineComments && !this.formatter.preferences.comment_format_line_comment_starting_on_first_column) ||
					 this.formatter.preferences.never_indent_line_comments_on_first_column)
    			) {
	   			commentIndentationLevel = this.column - 1;
    		} else {
    			// Indentation may be specific for contiguous comment
    			// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=293300
				if (this.lastLineComment.contiguous) {
					// The leading spaces have been set while looping in the printComment(int) method
					int currentCommentIndentation = getCurrentIndentation(this.lastLineComment.leadingSpaces, 0);
					// Keep the current comment indentation when over the previous contiguous line comment
					// and the previous comment has not been reindented
					int relativeIndentation = currentCommentIndentation - this.lastLineComment.currentIndentation;
					boolean similarCommentsIndentation = false;
					if (this.tabLength == 0) {
						similarCommentsIndentation = relativeIndentation == 0;
					} else if (relativeIndentation > -this.tabLength) {
						similarCommentsIndentation = relativeIndentation == 0 || currentCommentIndentation != 0 && this.lastLineComment.currentIndentation != 0;
					}
					if (similarCommentsIndentation && this.lastLineComment.indentation != this.indentationLevel) {
						int currentIndentationLevel = this.indentationLevel;
						this.indentationLevel = this.lastLineComment.indentation ;
						printIndentationIfNecessary();
						this.indentationLevel = currentIndentationLevel;
			   			commentIndentationLevel = this.lastLineComment.indentation ;
					} else {
						printIndentationIfNecessary();
			   			commentIndentationLevel = this.column - 1;
					}
				} else {
					if (this.currentAlignment != null && this.currentAlignment.kind == Alignment.ARRAY_INITIALIZER &&
						this.currentAlignment.fragmentCount > 0 &&
						this.indentationLevel < this.currentAlignment.breakIndentationLevel &&
						this.lastLineComment.lines > 0)
					{
						int currentIndentationLevel = this.indentationLevel;
						this.indentationLevel = this.currentAlignment.breakIndentationLevel;
		    			printIndentationIfNecessary();
						this.indentationLevel = currentIndentationLevel;
			   			commentIndentationLevel = this.currentAlignment.breakIndentationLevel;
					} else {
		    			printIndentationIfNecessary();
			   			commentIndentationLevel = this.column - 1;
					}
				}
    		}
    	}
    	
    	// Store line comment information
   		this.lastLineComment.contiguous = true;
		this.lastLineComment.currentIndentation = getCurrentCommentIndentation(currentTokenStartPosition);
		this.lastLineComment.indentation = commentIndentationLevel;
		
		// Add pending space if necessary
    	if (this.pendingSpace) {
    		if (this.formatter.preferences.comment_preserve_white_space_between_code_and_line_comments) {
    			addInsertEdit(currentTokenStartPosition, new String(this.lastLineComment.leadingSpaces));
    		} else {
    			addInsertEdit(currentTokenStartPosition, " "); //$NON-NLS-1$
    		}
    	}
    	this.needSpace = false;
    	this.pendingSpace = false;
    	int previousStart = currentTokenStartPosition;

		if (!isNlsTag && includesLineComments && (!onFirstColumn || this.formatter.preferences.comment_format_line_comment_starting_on_first_column)) {
			printLineComment(currentTokenStartPosition, currentTokenEndPosition-1);
		} else {
			// do nothing!?
	    	loop: while (nextCharacterStart <= currentTokenEndPosition && (currentCharacter = this.scanner.getNextChar()) != -1) {
	    		nextCharacterStart = this.scanner.currentPosition;

	    		switch(currentCharacter) {
	    			case '\r' :
	    				start = previousStart;
	    				break loop;
	    			case '\n' :
	    				start = previousStart;
	    				break loop;
	    		}
	    		previousStart = nextCharacterStart;
	    	}
	    	if (start != currentTokenStartPosition) {
	    		// this means that the line comment doesn't end the file
	    		addReplaceEdit(start, currentTokenEndPosition - 1, this.lineSeparator);
	    		this.line++;
	    		this.column = 1;
	    		this.lastNumberOfNewLines = 1;
	    	}
		}
    	this.needSpace = false;
    	this.pendingSpace = false;
    	// realign to the proper value
    	if (this.currentAlignment != null) {
    		if (this.memberAlignment != null) {
    			// select the last alignment
    			if (this.currentAlignment.location.inputOffset > this.memberAlignment.location.inputOffset) {
    				if (this.currentAlignment.couldBreak() && this.currentAlignment.wasSplit) {
    					this.currentAlignment.performFragmentEffect();
    				}
    			} else {
    				this.indentationLevel = Math.max(this.indentationLevel, this.memberAlignment.breakIndentationLevel);
    			}
    		} else if (this.currentAlignment.couldBreak() && this.currentAlignment.wasSplit) {
    			this.currentAlignment.performFragmentEffect();
    		}
    		if (this.currentAlignment.kind == Alignment.BINARY_EXPRESSION &&
    			this.currentAlignment.enclosing != null &&
    			this.currentAlignment.enclosing.kind == Alignment.BINARY_EXPRESSION &&
    			this.indentationLevel < this.currentAlignment.breakIndentationLevel)
    		{
    			this.indentationLevel = this.currentAlignment.breakIndentationLevel;
    		}
    	}
    	this.scanner.resetTo(currentTokenEndPosition, this.scannerEndPosition - 1);
    }

	private void printLineComment(int commentStart, int commentEnd) {

		// Compute indentation
		int firstColumn = this.column;
		int indentLevel = this.indentationLevel;
		int indentations = this.numberOfIndentations;
		this.indentationLevel = getNextIndentationLevel(firstColumn);
		if (this.indentationSize != 0) {
			this.numberOfIndentations = this.indentationLevel / this.indentationSize;
		}
		else{
			this.numberOfIndentations = 0;
		}

		// Consume the comment prefix
		this.scanner.resetTo(commentStart, commentEnd);
		this.scanner.getNextChar();
		this.scanner.getNextChar();
		this.column += 2;

		// Scan the text token per token to compact it and size it the max line length
		int maxColumn = this.formatter.preferences.comment_line_length + 1;
		int previousToken = -1;
		int lastTokenEndPosition = commentStart;
		int spaceStartPosition = -1;
		int spaceEndPosition = -1;
		this.scanner.skipComments = true;
		String newLineString = null;
		this.commentIndentation = null;

		// Consume text token per token
		while (!this.scanner.atEnd()) {
			int token;
			try {
				token = this.scanner.getNextToken();
			} catch (InvalidInputException iie) {
	    		token = consumeInvalidToken(commentEnd);
			}
			switch (token) {
				case TerminalTokens.TokenNameWHITESPACE:
					if (previousToken == -1) {
						// do not remember the first whitespace
						previousToken = SKIP_FIRST_WHITESPACE_TOKEN;
					} else {
						previousToken = token;
					}
					// Remember space position
					spaceStartPosition = this.scanner.getCurrentTokenStartPosition();
					spaceEndPosition = this.scanner.getCurrentTokenEndPosition();
					continue;
				case TerminalTokens.TokenNameEOF:
					continue;
				case TerminalTokens.TokenNameIdentifier:
					if (previousToken == -1 || previousToken == SKIP_FIRST_WHITESPACE_TOKEN) {
						char[] identifier = this.scanner.getCurrentTokenSource();
						int startPosition = this.scanner.getCurrentTokenStartPosition();
						int restartPosition = this.scanner.currentPosition;
						if (CharOperation.equals(identifier, Parser.FALL_THROUGH_TAG, 0, 5/*length of string "$FALL"*/) && this.scanner.currentCharacter == '-') {
							try {
								this.scanner.getNextToken(); //  consume the '-'
								token = this.scanner.getNextToken(); // consume the "THROUGH"
								if (token == TerminalTokens.TokenNameIdentifier) {
									identifier = this.scanner.getCurrentTokenSource();
									if (CharOperation.endsWith(Parser.FALL_THROUGH_TAG, identifier)) {
										// the comment starts with a fall through
										if (previousToken == SKIP_FIRST_WHITESPACE_TOKEN) {
											addReplaceEdit(spaceStartPosition, startPosition-1, " "); //$NON-NLS-1$
										}
										this.scanner.startPosition = startPosition;
										previousToken = token;
										break;
									}
								}
							} catch (InvalidInputException iie) {
								// skip
							}
						}
						// this was not a valid fall-through tag, hence continue to process the comment normally
						this.scanner.startPosition = startPosition;
			    		this.scanner.currentPosition = restartPosition;
					}
					break;
			}
			int tokenStart = this.scanner.getCurrentTokenStartPosition();
    		int tokenLength = (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - tokenStart;

			// insert space at the beginning if not present
			if (previousToken == -1 ) {
    			addInsertEdit(this.scanner.startPosition, " "); //$NON-NLS-1$
				this.column++;
			}
			// replace space at the beginning if present
			else if (previousToken == SKIP_FIRST_WHITESPACE_TOKEN) {
				addReplaceEdit(spaceStartPosition, this.scanner.startPosition-1, " "); //$NON-NLS-1$
				this.column++;
				spaceStartPosition = -1; // do not use this position to split the comment
			} else {
				// not on the first token
				boolean insertSpace = previousToken == TerminalTokens.TokenNameWHITESPACE;
				if (insertSpace) {
					// count inserted space if any in token length
					tokenLength++;
				}
				// insert new line if max line width is reached and a space was previously encountered
				if (spaceStartPosition > 0 && (this.column+tokenLength) > maxColumn) {
					this.lastNumberOfNewLines++;
					this.line++;
					if (newLineString == null) {
						this.tempBuffer.setLength(0);
						this.tempBuffer.append(this.lineSeparator);
						this.column = 1;
						if (!this.formatter.preferences.never_indent_line_comments_on_first_column) {
							printIndentationIfNecessary(this.tempBuffer);
						}
					    this.tempBuffer.append(LINE_COMMENT_PREFIX);
						this.column += LINE_COMMENT_PREFIX_LENGTH;
						newLineString = this.tempBuffer.toString();
				    	firstColumn = this.column;
					} else {
						this.column = firstColumn;
					}
					if (lastTokenEndPosition > spaceEndPosition) {
						this.column += lastTokenEndPosition - (spaceEndPosition + 1); // add all previous tokens lengths since last space
					}
					if (this.edits[this.editsIndex-1].offset == spaceStartPosition) {
						// previous space was already edited, so remove it
						this.editsIndex--;
					}
					addReplaceEdit(spaceStartPosition, spaceEndPosition, newLineString);
					spaceStartPosition = -1;
					if (insertSpace) {
						tokenLength--; // reduce token length as the space will be replaced by the new line
					}
				}
				// replace space if needed
				else if (insertSpace) {
					addReplaceEdit(spaceStartPosition, this.scanner.startPosition-1, " "); //$NON-NLS-1$
				}
			}
			// update column position and store info of the current token
			this.column += tokenLength;
			previousToken = token;
			lastTokenEndPosition = this.scanner.currentPosition;
		}
		this.scanner.skipComments = false;

		// Skip separator if the comment is not at the end of file
		this.indentationLevel = indentLevel;
		this.numberOfIndentations = indentations;
		this.lastNumberOfNewLines = 0;
		this.scanner.resetTo(lastTokenEndPosition, commentEnd);
		while (!this.scanner.atEnd()) {
			spaceEndPosition = this.scanner.currentPosition;
			this.scanner.getNextChar();
			if (this.scanner.currentCharacter == '\n' || this.scanner.currentCharacter == '\r') {
				// line comment is normally ended with new line
				this.column = 1;
				this.line++;
				this.lastNumberOfNewLines++;
				break;
			}
		}

		// Replace the line separator at the end of the comment if any...
		int startReplace = previousToken == SKIP_FIRST_WHITESPACE_TOKEN ? spaceStartPosition : lastTokenEndPosition;
		if (this.column == 1 && commentEnd >= startReplace) {
			addReplaceEdit(startReplace, commentEnd, this.formatter.preferences.line_separator);
		}
	}

	public void printEmptyLines(int linesNumber) {
		this.printEmptyLines(linesNumber, this.scanner.getCurrentTokenEndPosition() + 1);
	}

	private void printEmptyLines(int linesNumber, int insertPosition) {
		final String buffer = getEmptyLines(linesNumber);
		if (Util.EMPTY_STRING == buffer) return;
		addInsertEdit(insertPosition, buffer);
	}

	void printIndentationIfNecessary() {
		this.tempBuffer.setLength(0);
		printIndentationIfNecessary(this.tempBuffer);
		if (this.tempBuffer.length() > 0) {
			addInsertEdit(this.scanner.getCurrentTokenStartPosition(), this.tempBuffer.toString());
			this.pendingSpace = false;
		}
	}

	private void printIndentationIfNecessary(StringBuffer buffer) {
		switch(this.tabChar) {
			case DefaultCodeFormatterOptions.TAB :
				boolean useTabsForLeadingIndents = this.useTabsOnlyForLeadingIndents;
				int numberOfLeadingIndents = this.numberOfIndentations;
				int indentationsAsTab = 0;
				if (useTabsForLeadingIndents) {
					while (this.column <= this.indentationLevel) {
						if (this.tabLength > 0 && indentationsAsTab < numberOfLeadingIndents) {
							if (buffer != null) buffer.append('\t');
							indentationsAsTab++;
							int complement = this.tabLength - ((this.column - 1) % this.tabLength); // amount of space
							this.column += complement;
						} else {
							if (buffer != null) buffer.append(' ');
							this.column++;
						}
						this.needSpace = false;
					}
				} else if (this.tabLength > 0) {
					while (this.column <= this.indentationLevel) {
						if (buffer != null) buffer.append('\t');
						int complement = this.tabLength - ((this.column - 1) % this.tabLength); // amount of space
						this.column += complement;
						this.needSpace = false;
					}
				}
				break;
			case DefaultCodeFormatterOptions.SPACE :
				while (this.column <= this.indentationLevel) {
					if (buffer != null) buffer.append(' ');
					this.column++;
					this.needSpace = false;
				}
				break;
			case DefaultCodeFormatterOptions.MIXED :
				useTabsForLeadingIndents = this.useTabsOnlyForLeadingIndents;
				numberOfLeadingIndents = this.numberOfIndentations;
				indentationsAsTab = 0;
				if (useTabsForLeadingIndents) {
					final int columnForLeadingIndents = numberOfLeadingIndents * this.indentationSize;
					while (this.column <= this.indentationLevel) {
						if (this.column <= columnForLeadingIndents) {
							if (this.tabLength > 0 && (this.column - 1 + this.tabLength) <= this.indentationLevel) {
								if (buffer != null) buffer.append('\t');
								this.column += this.tabLength;
							} else if ((this.column - 1 + this.indentationSize) <= this.indentationLevel) {
								// print one indentation
								// note that this.indentationSize > 0 when entering in the following loop
								// hence this.column will be incremented and then avoid endless loop (see bug 290905)
								for (int i = 0, max = this.indentationSize; i < max; i++) {
									if (buffer != null) buffer.append(' ');
									this.column++;
								}
							} else {
								if (buffer != null) buffer.append(' ');
								this.column++;
							}
						} else {
							for (int i = this.column, max = this.indentationLevel; i <= max; i++) {
								if (buffer != null) buffer.append(' ');
								this.column++;
							}
						}
						this.needSpace = false;
					}
				} else {
					while (this.column <= this.indentationLevel) {
						if (this.tabLength > 0 && (this.column - 1 + this.tabLength) <= this.indentationLevel) {
							if (buffer != null) buffer.append('\t');
							this.column += this.tabLength;
						} else if (this.indentationSize > 0 && (this.column - 1 + this.indentationSize) <= this.indentationLevel) {
							// print one indentation
							for (int i = 0, max = this.indentationSize; i < max; i++) {
								if (buffer != null) buffer.append(' ');
								this.column++;
							}
						} else {
							if (buffer != null) buffer.append(' ');
							this.column++;
						}
						this.needSpace = false;
					}
				}
				break;
		}
	}

	private void printJavadocBlock(FormatJavadocBlock block) {
		if( block == null) return;

		// Init positions
		int previousEnd = block.tagEnd;
		int maxNodes = block.nodesPtr;

		// Compute indentation
		boolean headerLine = block.isHeaderLine() && this.lastNumberOfNewLines == 0;
		int maxColumn = this.formatter.preferences.comment_line_length + 1;
		if (headerLine) {
			maxColumn++;
		}

		// format tag section if necessary
		if (!block.isInlined()) {
			this.lastNumberOfNewLines = 0;
		}
		if (block.isDescription()) {
			if (!block.isInlined()) {
			    this.commentIndentation = null;
			}
		} else {
			int tagLength = previousEnd - block.sourceStart + 1;
			this.column += tagLength;
			if (!block.isInlined()) {
			    boolean indentRootTags = this.formatter.preferences.comment_indent_root_tags && !block.isInDescription();
			    int commentIndentationLevel = 0;
				if (indentRootTags) {
				    commentIndentationLevel = tagLength + 1;
					boolean indentParamTag = this.formatter.preferences.comment_indent_parameter_description && block.isInParamTag();
					if (indentParamTag) {
						commentIndentationLevel += this.indentationSize;
					}
				}
				setCommentIndentation(commentIndentationLevel);
			}
			FormatJavadocReference reference= block.reference;
			if (reference != null) {
				// format reference
				printJavadocBlockReference(block, reference);
				previousEnd = reference.sourceEnd;
			}

			// Nothing else to do if the tag has no node
			if (maxNodes < 0)  {
				if (block.isInlined()) {
					this.column++;
				}
				return;
			}
		}

		// tag section: iterate through the blocks composing this tag but the last one
		int previousLine = Util.getLineNumber(previousEnd, this.lineEnds, 0, this.maxLines);
		boolean clearBlankLines = this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment;
		boolean joinLines = this.formatter.preferences.join_lines_in_comments;
		for (int i=0; i<=maxNodes; i++) {
			FormatJavadocNode node = block.nodes[i];
			int nodeStart = node.sourceStart;

			// Print empty lines before the node
			int newLines;
			if (i == 0) {
				newLines = this.formatter.preferences.comment_insert_new_line_for_parameter && block.isParamTag() ? 1 : 0;
				if (nodeStart > (previousEnd+1)) {
					if (!clearBlankLines || !joinLines) {
						int startLine = Util.getLineNumber(nodeStart, this.lineEnds, previousLine-1, this.maxLines);
						int gapLine = previousLine;
						if (joinLines) gapLine++; // if not preserving line break then gap must be at least of one line
						if (startLine > gapLine) {
							newLines = startLine - previousLine;
						}
						if (clearBlankLines) {
							// clearing blank lines in this block means that break lines should be preserved, hence only keep one new line
							if (newLines > 0)  newLines = 1;
						}
					}
					if (newLines == 0 && (!node.isImmutable() || block.reference != null)) {
						newLines = printJavadocBlockNodesNewLines(block, node, previousEnd);
					}
					if (block.isImmutable()) {
						printJavadocGapLinesForImmutableBlock(block);
					} else {
						printJavadocGapLines(previousEnd+1, nodeStart-1, newLines, clearBlankLines, false, null);
					}
				} else {
					this.tempBuffer.setLength(0);
					if (newLines > 0) {
						for (int j=0; j<newLines; j++) {
							printJavadocNewLine(this.tempBuffer);
						}
						addInsertEdit(nodeStart, this.tempBuffer.toString());
					}
				}
			} else {
				newLines = this.column > maxColumn ? 1 : 0;
				if (!clearBlankLines && node.lineStart > (previousLine+1)) newLines = node.lineStart - previousLine;
				if (newLines < node.linesBefore) newLines = node.linesBefore;
				if (newLines == 0) {
					newLines = printJavadocBlockNodesNewLines(block, node, previousEnd);
				}
				if (newLines > 0 || nodeStart > (previousEnd+1)) {
		   			printJavadocGapLines(previousEnd+1, nodeStart-1, newLines, clearBlankLines, false, null);
				}
			}
			if (headerLine && newLines > 0) {
				headerLine = false;
				maxColumn--;
			}

			// Print node
			if (node.isText()) {
				FormatJavadocText text = (FormatJavadocText) node;
				if (text.isImmutable()) {
					// Indent if new line was added
					if (text.isImmutableHtmlTag() && newLines > 0 && this.commentIndentation != null) {
				    	addInsertEdit(node.sourceStart, this.commentIndentation);
				    	this.column += this.commentIndentation.length();
					}
					printJavadocImmutableText(text, block, newLines > 0);
					this.column += getTextLength(block, text);
				} else if (text.isHtmlTag()) {
					printJavadocHtmlTag(text, block, newLines>0);
				} else {
					printJavadocText(text, block, newLines>0);
				}
			} else {
				if (newLines > 0 && this.commentIndentation != null) {
			    	addInsertEdit(node.sourceStart, this.commentIndentation);
			    	this.column += this.commentIndentation.length();
				}
				printJavadocBlock((FormatJavadocBlock)node);
			}

			// Print empty lines before the node
			previousEnd = node.sourceEnd;
			previousLine = Util.getLineNumber(previousEnd, this.lineEnds, node.lineStart > 1 ? node.lineStart-2 : 0, this.maxLines);
		}
		this.lastNumberOfNewLines = 0;
	}

	private int printJavadocBlockNodesNewLines(FormatJavadocBlock block, FormatJavadocNode node, int previousEnd) {
	   	int maxColumn = this.formatter.preferences.comment_line_length+1;
    	int nodeStart = node.sourceStart;
 	    try {
			this.scanner.resetTo(nodeStart , node.sourceEnd);
	    	int length = 0;
	    	boolean newLine = false;
			boolean headerLine = block.isHeaderLine() && this.lastNumberOfNewLines == 0;
			int firstColumn = 1 + this.indentationLevel + BLOCK_LINE_PREFIX_LENGTH;
			if (this.commentIndentation != null) firstColumn += this.commentIndentation.length();
			if (headerLine) maxColumn++;
			FormatJavadocText text = null;
			boolean isImmutableNode = node.isImmutable();
			boolean nodeIsText = node.isText();
			if (nodeIsText) {
	    		text = (FormatJavadocText)node;
			} else {
				FormatJavadocBlock inlinedBlock = (FormatJavadocBlock)node;
				if (isImmutableNode) {
					text = (FormatJavadocText) inlinedBlock.getLastNode();
					if (text != null) {
			    		length += inlinedBlock.tagEnd - inlinedBlock.sourceStart + 1;  // tag length
				    	if (nodeStart > (previousEnd+1)) {
				    		length++; // include space between nodes
				    	}
						this.scanner.resetTo(text.sourceStart , node.sourceEnd);
					}
				}
			}
	    	if (text != null) {
    			if (isImmutableNode) {
			    	if (nodeStart > (previousEnd+1)) {
			    		length++; // include space between nodes
			    	}
    				int lastColumn = this.column + length;
	    			while (!this.scanner.atEnd()) {
	    				try {
		    				int token = this.scanner.getNextToken();
		    				switch (token) {
		    					case TerminalTokens.TokenNameWHITESPACE:
		    						if (CharOperation.indexOf('\n', this.scanner.source, this.scanner.startPosition, this.scanner.currentPosition) >= 0) {
		    							return 0;
		    						}
									lastColumn = getCurrentIndentation(this.scanner.getCurrentTokenSource(), lastColumn);
		    						break;
		    					case TerminalTokens.TokenNameMULTIPLY:
		    						if (newLine) {
		    							newLine = false;
		    							continue;
		    						}
		    						lastColumn++;
		    						break;
		    					default:
					    			lastColumn += (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - this.scanner.startPosition;
		    						break;
		    				}
	    				}
	    				catch (InvalidInputException iie) {
	    					// maybe an unterminated string or comment
			    			lastColumn += (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - this.scanner.startPosition;
	    				}
	    				if (lastColumn > maxColumn) {
	    					return 1;
						}
	    			}
	    			return 0;
    			}
    			if (text.isHtmlTag()) {
    				if (text.getHtmlTagID() == JAVADOC_SINGLE_BREAK_TAG_ID) {
    					// never break before single break tag
    					return 0;
    				}
	    			// read the html tag
    				this.scanner.getNextToken();
	    			if (this.scanner.getNextToken() == TerminalTokens.TokenNameDIVIDE) {
	    				length++;
	    				this.scanner.getNextToken();
	    			}
	    			length += (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - this.scanner.startPosition;
	    			this.scanner.getNextToken(); // '>'
	    			length++;
	    		} else {
	    			while (true) {
	    				int token = this.scanner.getNextToken();
	    				if (token == TerminalTokens.TokenNameWHITESPACE || token == TerminalTokens.TokenNameEOF) break;
		    			int tokenLength = (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - this.scanner.startPosition;
		    			length += tokenLength;
		    			if ((this.column + length) >= maxColumn) {
		    				break;
		    			}
	    			}
	    		}
	    	} else {
	    		FormatJavadocBlock inlinedBlock = (FormatJavadocBlock) node;
	    		length += inlinedBlock.tagEnd - inlinedBlock.sourceStart + 1;  // tag length
	    		if (inlinedBlock.reference != null) {
		    		length++; // space between tag and reference
					this.scanner.resetTo(inlinedBlock.reference.sourceStart, inlinedBlock.reference.sourceEnd);
					int previousToken = -1;
					loop: while (!this.scanner.atEnd()) {
						int token = this.scanner.getNextToken();
			    		int tokenLength = (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - this.scanner.startPosition;
						switch (token) {
							case TerminalTokens.TokenNameWHITESPACE:
								if (previousToken == TerminalTokens.TokenNameCOMMA) { // space between method arguments
									length++;
								}
								break;
							case TerminalTokens.TokenNameMULTIPLY:
								break;
							default:
								length += tokenLength;
								if ((this.column+length) > maxColumn) {
									break loop;
								}
					    		break;
						}
						previousToken = token;
		    		}
	    		}
	    		length++; // one more for closing brace
	    	}
	    	if (nodeStart > (previousEnd+1)) {
	    		length++; // include space between nodes
	    	}
    		if ((firstColumn + length) >= maxColumn && node == block.nodes[0]) {
    			// Do not split in this peculiar case as length would be also over the max
    			// length on next line
    			return 0;
    		}
			if ((this.column + length) > maxColumn) {
	    		return 1;
	    	}
	    } catch (InvalidInputException iie) {
	    	// Assume length is one
	    	int tokenLength = 1;
	    	if (nodeStart > (previousEnd+1)) {
	    		tokenLength++; // include space between nodes
	    	}
			if ((this.column + tokenLength) > maxColumn) {
	    		return 1;
	    	}
	    }
	    return 0;
    }

	private void printJavadocBlockReference(FormatJavadocBlock block, FormatJavadocReference reference) {
		int maxColumn = this.formatter.preferences.comment_line_length + 1;
		boolean headerLine = block.isHeaderLine();
		boolean inlined = block.isInlined();
		if (headerLine) maxColumn++;

		// First we need to know what is the indentation
		this.scanner.resetTo(block.tagEnd+1, reference.sourceEnd);
		this.javadocBlockRefBuffer.setLength(0);
		boolean needFormat = false;
		int previousToken = -1;
		int spacePosition = -1;
		String newLineString = null;
		int firstColumn = -1;
		while (!this.scanner.atEnd()) {
			int token;
			try {
				token = this.scanner.getNextToken();
	    		int tokenLength = (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - this.scanner.startPosition;
				switch (token) {
					case TerminalTokens.TokenNameWHITESPACE:
						if (previousToken != -1 || tokenLength > 1 || this.scanner.currentCharacter != ' ') needFormat = true;
						switch (previousToken) {
							case TerminalTokens.TokenNameMULTIPLY :
							case TerminalTokens.TokenNameLPAREN:
								break;
							default:	// space between method arguments
								spacePosition = this.javadocBlockRefBuffer.length();
								// $FALL-THROUGH$ - fall through next case
							case -1:
								this.javadocBlockRefBuffer.append(' ');
								this.column++;
								break;
						}
						break;
					case TerminalTokens.TokenNameMULTIPLY:
						break;
					default:
						if (!inlined && spacePosition > 0 && (this.column+tokenLength) > maxColumn) {
							// not enough space on the line
							this.lastNumberOfNewLines++;
							this.line++;
							if (newLineString == null) {
								this.tempBuffer.setLength(0);
								this.tempBuffer.append(this.lineSeparator);
						    	this.column = 1;
						    	printIndentationIfNecessary(this.tempBuffer);
					    		this.tempBuffer.append(BLOCK_LINE_PREFIX);
					    		this.column += BLOCK_LINE_PREFIX_LENGTH;
								if (this.commentIndentation != null) {
							    	this.tempBuffer.append(this.commentIndentation);
							    	this.column += this.commentIndentation.length();
						    	}
						    	newLineString = this.tempBuffer.substring(0, this.tempBuffer.length()-1); // remove last space as buffer will be inserted before a space
						    	firstColumn = this.column;
							} else {
								this.column = firstColumn;
							}
							this.column = firstColumn + this.javadocBlockRefBuffer.length() - spacePosition - 1;
							this.javadocBlockRefBuffer.insert(spacePosition, newLineString);
							if (headerLine) {
								headerLine = false;
								maxColumn--;
							}
							spacePosition = -1;
						}
						this.javadocBlockRefBuffer.append(this.scanner.source, this.scanner.startPosition, tokenLength);
			    		this.column += tokenLength;
			    		break;
				}
				previousToken = token;
			} catch (InvalidInputException iie) {
				// does not happen as syntax is correct
			}
		}
		if (needFormat) {
		    addReplaceEdit(block.tagEnd+1, reference.sourceEnd, this.javadocBlockRefBuffer.toString());
		}
    }

	private int getTextLength(FormatJavadocBlock block, FormatJavadocText text) {

		// Special case for immutable tags
		if (text.isImmutable()) {
			this.scanner.resetTo(text.sourceStart , text.sourceEnd);
			int textLength = 0;
			while (!this.scanner.atEnd()) {
				try {
	                int token = this.scanner.getNextToken();
	    			if (token == TerminalTokens.TokenNameWHITESPACE) {
						if (CharOperation.indexOf('\n', this.scanner.source, this.scanner.startPosition, this.scanner.currentPosition) >= 0) {
							textLength = 0;
							this.scanner.getNextChar();
							if (this.scanner.currentCharacter == '*') {
								this.scanner.getNextChar();
								if (this.scanner.currentCharacter != ' ') {
									textLength++;
								}
							} else {
								textLength++;
							}
							continue;
						}
	    			}
	    			textLength += (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - this.scanner.startPosition;
                } catch (InvalidInputException e) {
   					// maybe an unterminated string or comment
	    			textLength += (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - this.scanner.startPosition;
                }
			}
			return textLength;
		}

		// Simple for one line tags
	    if (block.isOneLineTag()) {
	    	return text.sourceEnd - text.sourceStart + 1;
	    }

	    // Find last line
    	int startLine = Util.getLineNumber(text.sourceStart, this.lineEnds, 0, this.maxLines);
    	int endLine = startLine;
    	int previousEnd = -1;
    	for (int i=0; i<=text.separatorsPtr; i++) {
    		int end = (int) (text.separators[i] >>> 32);
    		endLine = Util.getLineNumber(end, this.lineEnds, endLine-1, this.maxLines);
    		if (endLine > startLine) {
    			return previousEnd - text.sourceStart + 1;
    		}
    		previousEnd = end;
    	}

    	// This was a one line text
		return text.sourceEnd - text.sourceStart + 1;
    }

	/*
	 * Print and formats a javadoc comments
	 */
	void printJavadocComment(int start, int end) {
		int lastIndentationLevel = this.indentationLevel;
		try {
			// parse the comment on the fly
			this.scanner.resetTo(start, end-1);
			if (! this.formatterCommentParser.parse(start, end-1)) {
				// problem occurred while parsing the javadoc, early abort formatting
				return;
			}

			FormatJavadoc javadoc = (FormatJavadoc) this.formatterCommentParser.docComment;

			// handle indentation
			if (this.indentationLevel != 0) {
				printIndentationIfNecessary();
			}

			// handle pending space if any
			if (this.pendingSpace) {
				addInsertEdit(start, " "); //$NON-NLS-1$
			}

			if (javadoc.blocks == null) {
				// no FormatJavadocTags in this this javadoc
				return;
			}

			// init properly
			this.needSpace = false;
			this.pendingSpace = false;
			int length = javadoc.blocks.length;

			// format empty lines between before the first block
			FormatJavadocBlock previousBlock = javadoc.blocks[0];
			this.lastNumberOfNewLines = 0;
			int currentLine = this.line;
			int firstBlockStart = previousBlock.sourceStart;
			printIndentationIfNecessary(null);
			this.column += JAVADOC_HEADER_LENGTH; // consider that the header is already scanned

			// If there are several blocks in the javadoc
			int index = 1;
			if (length > 1) {
				// format the description if any
				if (previousBlock.isDescription()) {
					printJavadocBlock(previousBlock);
					FormatJavadocBlock block = javadoc.blocks[index++];
					int newLines = this.formatter.preferences.comment_insert_empty_line_before_root_tags ? 2 : 1;
					printJavadocGapLines(previousBlock.sourceEnd+1, block.sourceStart-1, newLines, this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, false, null);
					previousBlock = block;
				}

				// format all tags but the last one composing this comment
				while (index < length) {
					printJavadocBlock(previousBlock);
					FormatJavadocBlock block = javadoc.blocks[index++];
					printJavadocGapLines(previousBlock.sourceEnd+1, block.sourceStart-1, 1, this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, false, null);
					previousBlock = block;
				}
			}

			// format the last block
			printJavadocBlock(previousBlock);

			// format the header and footer empty spaces
			int newLines = (this.formatter.preferences.comment_new_lines_at_javadoc_boundaries && (this.line > currentLine || javadoc.isMultiLine())) ? 1 : 0;
			printJavadocGapLines(javadoc.textStart, firstBlockStart-1, newLines, this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, false, null);
			printJavadocGapLines(previousBlock.sourceEnd+1, javadoc.textEnd, newLines, this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, true, null);
		}
		finally {
			// reset the scanner
			this.scanner.resetTo(end, this.scannerEndPosition - 1);
			this.needSpace = false;
			this.indentationLevel = lastIndentationLevel;
			this.lastNumberOfNewLines = 0;
		}
	}

	/*
	 * prints the empty javadoc line between the 2 given positions.
	 * May insert new '*' before each new line
	 */
	private void printJavadocGapLines(int textStartPosition, int textEndPosition, int newLines, boolean clearBlankLines, boolean footer, StringBuffer output) {
		try {
			// If no lines to set in the gap then just insert a space if there's enough room to
			if (newLines == 0) {
				if (output == null) {
					addReplaceEdit(textStartPosition, textEndPosition,  " "); //$NON-NLS-1$
				} else {
					output.append(' ');
				}
				this.column++;
				return;
			}

			// if there's no enough room to replace text, then insert the gap
			if (textStartPosition > textEndPosition) {
				if (newLines > 0) {
					this.javadocGapLinesBuffer.setLength(0);
					for (int i=0; i<newLines; i++) {
						this.javadocGapLinesBuffer.append(this.lineSeparator);
						this.column = 1;
						printIndentationIfNecessary(this.javadocGapLinesBuffer);
						if (footer) {
							this.javadocGapLinesBuffer.append(' ');
							this.column++;
						} else {
							this.javadocGapLinesBuffer.append(BLOCK_LINE_PREFIX);
							this.column += BLOCK_LINE_PREFIX_LENGTH;
						}
					}
					if (output == null) {
						addInsertEdit(textStartPosition, this.javadocGapLinesBuffer.toString());
					} else {
						output.append(this.javadocGapLinesBuffer);
					}
				}
				return;
			}

			// There's enough room and some lines to set...
			// Skip the text token per token to keep existing stars when possible
			this.scanner.resetTo(textStartPosition, textEndPosition);
			this.scanner.recordLineSeparator = true;
			this.scanner.linePtr = Util.getLineNumber(textStartPosition, this.lineEnds, 0, this.maxLines) - 2;
			int linePtr = this.scanner.linePtr;
			int lineCount = 0;
			int start = textStartPosition;
			boolean endsOnMultiply = false;
			while (!this.scanner.atEnd()) {
				switch (this.scanner.getNextToken()) {
					case TerminalTokens.TokenNameMULTIPLY:
						// we just need to replace each lines between '*' with the javadoc formatted ones
						int linesGap = this.scanner.linePtr - linePtr;
						if (linesGap > 0) {
							this.javadocGapLinesBuffer.setLength(0);
							if (lineCount > 0) {
								// TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=49619
								this.javadocGapLinesBuffer.append( ' ');
							}
							for (int i = 0; i < linesGap ; i++) {
								if (clearBlankLines && lineCount >= newLines) {
									// leave as the required new lines have been inserted
									// so remove any remaining blanks and leave
									if (textEndPosition >= start) {
										if (output == null) {
											addReplaceEdit(start, textEndPosition, this.javadocGapLinesBuffer.toString());
										} else {
											output.append(this.javadocGapLinesBuffer);
										}
									}
									return;
								}
								this.javadocGapLinesBuffer.append(this.lineSeparator);
								this.column = 1;
								printIndentationIfNecessary(this.javadocGapLinesBuffer);
								if (i == (linesGap-1)) {
									this.javadocGapLinesBuffer.append(' ');
									this.column++;
								} else {
									this.javadocGapLinesBuffer.append(BLOCK_LINE_PREFIX);
									this.column += BLOCK_LINE_PREFIX_LENGTH;
								}
								lineCount++;
							}
							int currentTokenStartPosition = this.scanner.getCurrentTokenStartPosition();
							int tokenLength = this.scanner.currentPosition - currentTokenStartPosition;
							if (output == null) {
								addReplaceEdit(start, currentTokenStartPosition-1, this.javadocGapLinesBuffer.toString());
							} else {
								output.append(this.javadocGapLinesBuffer);
								output.append(this.scanner.source, currentTokenStartPosition, tokenLength);
							}
							this.column += tokenLength;
							if (footer && clearBlankLines && lineCount == newLines) {
								if (textEndPosition >= currentTokenStartPosition) {
									if (output == null) {
										addDeleteEdit(currentTokenStartPosition, textEndPosition);
									}
								}
								return;
							}
						}
						// next start is just after the current token
						start = this.scanner.currentPosition;
						linePtr = this.scanner.linePtr;
						endsOnMultiply = true;
						break;
					default:
						endsOnMultiply = false;
						break;
				}
			}

			// Format the last whitespaces
			if (lineCount < newLines) {
				// Insert new lines as not enough was encountered while scanning the whitespaces
				this.javadocGapLinesBuffer.setLength(0);
				if (lineCount > 0) {
					// TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=49619
					this.javadocGapLinesBuffer.append( ' ');
				}
				for (int i = lineCount; i < newLines-1; i++) {
					printJavadocNewLine(this.javadocGapLinesBuffer);
				}
				this.javadocGapLinesBuffer.append(this.lineSeparator);
				this.column = 1;
				printIndentationIfNecessary(this.javadocGapLinesBuffer);
				if (footer) {
					this.javadocGapLinesBuffer.append(' ');
					this.column++;
				} else {
					this.javadocGapLinesBuffer.append(BLOCK_LINE_PREFIX);
					this.column += BLOCK_LINE_PREFIX_LENGTH;
				}
				if (output == null) {
					if (textEndPosition >= start) {
						addReplaceEdit(start, textEndPosition, this.javadocGapLinesBuffer.toString());
					} else {
						addInsertEdit(textEndPosition+1, this.javadocGapLinesBuffer.toString());
					}
				} else {
					output.append(this.javadocGapLinesBuffer);
				}
			} else {
				// Replace all remaining whitespaces by a single space
				if (textEndPosition >= start) {
					this.javadocGapLinesBuffer.setLength(0);
					if (this.scanner.linePtr > linePtr) {
						if (lineCount > 0) {
							// TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=49619
							this.javadocGapLinesBuffer.append(' ');
						}
						this.javadocGapLinesBuffer.append(this.lineSeparator);
						this.column = 1;
						printIndentationIfNecessary(this.javadocGapLinesBuffer);
					}
					this.javadocGapLinesBuffer.append(' ');
					if (output == null) {
						addReplaceEdit(start, textEndPosition, this.javadocGapLinesBuffer.toString());
					} else {
						output.append(this.javadocGapLinesBuffer);
					}
					this.needSpace = false;
				} else if (endsOnMultiply) {
					if (output == null) {
						addInsertEdit(textEndPosition+1, " "); //$NON-NLS-1$
					} else {
						output.append(' ');
					}
					this.needSpace = false;
				}
				this.column++;
			}
		}
		catch (InvalidInputException iie) {
			// there's nothing to do if this exception happens
		}
		finally {
			this.scanner.recordLineSeparator = false;
			this.needSpace = false;
			this.scanner.resetTo(textEndPosition+1, this.scannerEndPosition - 1);
			this.lastNumberOfNewLines += newLines;
			this.line += newLines;
		}
	}

	private void printJavadocImmutableText(FormatJavadocText text, FormatJavadocBlock block, boolean textOnNewLine) {

		try {
			// Iterate on text line separators
			int textLineStart = text.lineStart;
			this.scanner.tokenizeWhiteSpace = false;
			String newLineString = null;
			for (int idx=0, max=text.separatorsPtr; idx<=max ; idx++) {
				int start = (int) text.separators[idx];
				int lineStart = Util.getLineNumber(start, this.lineEnds, textLineStart-1, this.maxLines);
				while (textLineStart < lineStart) {
					int end = this.lineEnds[textLineStart-1];
					this.scanner.resetTo(end, start);
					int token = this.scanner.getNextToken();
					switch (token) {
						case TerminalTokens.TokenNameMULTIPLY:
						case TerminalTokens.TokenNameMULTIPLY_EQUAL:
							break;
						default:
							return;
					}
					if (this.scanner.currentCharacter == ' ') {
						this.scanner.getNextChar();
					}
					if (newLineString == null) {
						this.tempBuffer.setLength(0);
						this.column = 1;
						printIndentationIfNecessary(this.tempBuffer);
						this.tempBuffer.append(BLOCK_LINE_PREFIX);
						this.column += BLOCK_LINE_PREFIX_LENGTH;
						newLineString = this.tempBuffer.toString();
					}
					addReplaceEdit(end+1, this.scanner.getCurrentTokenEndPosition(), newLineString);
					textLineStart = Util.getLineNumber(this.scanner.currentPosition-1, this.lineEnds, textLineStart, this.maxLines);
				}
			}
		}
		catch (InvalidInputException iie) {
			// leave
		}
		finally {
			// Reset
			this.needSpace = false;
			this.scanner.tokenizeWhiteSpace = true;
			this.scanner.resetTo(text.sourceEnd+1, this.scannerEndPosition - 1);
		}
	}

	/*
	 *  Print the gap lines for an immutable block.
	 *  That's needed to  be specific as the formatter needs to keep white spaces
	 *  if possible except those which are indentation ones.
	 *  Note that in the peculiar case of a two lines immutable tag (multi lines block),
	 *  the formatter will join the two lines.
	 */
	private void printJavadocGapLinesForImmutableBlock(FormatJavadocBlock block) {

		// Init
		int firstLineEnd = -1; // not initialized
		int newLineStart = -1; // not initialized
		int secondLineStart = -1; // not initialized
		int starPosition = -1; // not initialized
		int offset = 0;
		int start = block.tagEnd + 1;
		int end = block.nodes[0].sourceStart-1;
		this.scanner.resetTo(start, end);
		int lineStart = block.lineStart;
		int lineEnd = Util.getLineNumber(block.nodes[0].sourceEnd, this.lineEnds, lineStart-1, this.maxLines);
		boolean multiLinesBlock = lineEnd > (lineStart+1);
		int previousPosition = this.scanner.currentPosition;
		String newLineString = null;
		int indentationColumn = 0;
		int leadingSpaces = -1;

		// Scan the existing gap
		while (!this.scanner.atEnd()) {
			char ch = (char) this.scanner.getNextChar();
			switch (ch) {
				case '\t' :
					// increase the corresponding counter from the appropriate tab value
					if (secondLineStart > 0 || firstLineEnd < 0) {
						int reminder = this.tabLength == 0 ? 0 : offset % this.tabLength;
						if (reminder == 0) {
							offset += this.tabLength;
						} else {
							offset = ((offset / this.tabLength) + 1) * this.tabLength;
						}
					} else if (leadingSpaces >= 0) {
						int reminder = this.tabLength == 0 ? 0 : offset % this.tabLength;
						if (reminder == 0) {
							leadingSpaces += this.tabLength;
						} else {
							leadingSpaces = ((offset / this.tabLength) + 1) * this.tabLength;
						}
					}
					break;
				case '\r' :
				case '\n' :
					// new line, store the end of the first one
					if (firstLineEnd < 0) {
						firstLineEnd = previousPosition;
					}
					// print indentation if there were spaces without any star on the line
					if (leadingSpaces > 0 && multiLinesBlock) {
						if (newLineString == null) {
							this.column = 1;
							this.tempBuffer.setLength(0);
							printIndentationIfNecessary(this.tempBuffer);
							this.tempBuffer.append(BLOCK_LINE_PREFIX);
							this.column += BLOCK_LINE_PREFIX_LENGTH;
							newLineString = this.tempBuffer.toString();
							indentationColumn = this.column;
						} else {
							this.column = indentationColumn;
						}
						addReplaceEdit(newLineStart, newLineStart+indentationColumn-2, newLineString);
					}
					// store line start and reset positions
					newLineStart = this.scanner.currentPosition;
					leadingSpaces = 0;
					starPosition = -1;
					if (multiLinesBlock) {
						offset = 0;
						secondLineStart = -1;
					}
					break;
				case '*' :
					// store line start position if this is the first star of the line
					if (starPosition < 0 && firstLineEnd > 0) {
						secondLineStart = this.scanner.currentPosition;
						starPosition = this.scanner.currentPosition;
						leadingSpaces = -1;
					}
					break;
				default :
					// increment offset if line has started
					if (secondLineStart > 0) {
						// skip first white space after the first '*'
						if (secondLineStart == starPosition) {
							secondLineStart = this.scanner.currentPosition;
						} else {
							// print indentation before the following characters
							if (offset == 0 && multiLinesBlock) {
								if (newLineString == null) {
									this.tempBuffer.setLength(0);
									this.column = 1;
									printIndentationIfNecessary(this.tempBuffer);
									this.tempBuffer.append(BLOCK_LINE_PREFIX);
									this.column += BLOCK_LINE_PREFIX_LENGTH;
									indentationColumn = this.column;
									newLineString = this.tempBuffer.toString();
								} else {
									this.column = indentationColumn;
								}
								addReplaceEdit(newLineStart, secondLineStart-1, newLineString);
							}
							offset++;
						}
					} else if (firstLineEnd < 0) {
						// no new line yet, increment the offset
						offset++;
					} else if (leadingSpaces >= 0) {
						// no star yet, increment the leading spaces
						leadingSpaces++;
					}
					break;
			}
			previousPosition = this.scanner.currentPosition;
		}
		
		// Increment the columns from the numbers of characters counted on the line
		if (multiLinesBlock) {
			this.column += offset;
		} else {
			this.column++;
		}
		
		// Replace the new line with a single space when there's only one separator
		// or, if necessary, print the indentation on the last line
		if (!multiLinesBlock) {
			if (firstLineEnd > 0) {
				addReplaceEdit(firstLineEnd, end, " "); //$NON-NLS-1$
			}
		}
		else if (secondLineStart > 0) {
			if (newLineString == null) {
				this.tempBuffer.setLength(0);
				this.column = 1;
				printIndentationIfNecessary(this.tempBuffer);
				this.tempBuffer.append(BLOCK_LINE_PREFIX);
				this.column += BLOCK_LINE_PREFIX_LENGTH;
				newLineString = this.tempBuffer.toString();
				indentationColumn = this.column;
			} else {
				this.column = indentationColumn;
			}
			addReplaceEdit(newLineStart, secondLineStart-1, newLineString);
		}
		else if (leadingSpaces > 0) {
			if (newLineString == null) {
				this.tempBuffer.setLength(0);
				this.column = 1;
				printIndentationIfNecessary(this.tempBuffer);
				this.tempBuffer.append(BLOCK_LINE_PREFIX);
				this.column += BLOCK_LINE_PREFIX_LENGTH;
				newLineString = this.tempBuffer.toString();
				indentationColumn = this.column;
			} else {
				this.column = indentationColumn;
			}
			addReplaceEdit(newLineStart, newLineStart+indentationColumn-2, newLineString);
		}

		// Reset
		this.needSpace = false;
		this.scanner.resetTo(end+1, this.scannerEndPosition - 1);
	}

	private int printJavadocHtmlTag(FormatJavadocText text, FormatJavadocBlock block, boolean textOnNewLine) {

		// Compute indentation if necessary
		boolean clearBlankLines = this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment;

		// Local variables init
		int textStart = text.sourceStart;
		int nextStart = textStart;
		int startLine = Util.getLineNumber(textStart, this.lineEnds, 0, this.maxLines);
	    int htmlTagID = text.getHtmlTagID();
	    if (text.depth >= this.javadocHtmlTagBuffers.length) {
	    	int length = this.javadocHtmlTagBuffers.length;
	    	System.arraycopy(this.javadocHtmlTagBuffers, 0, this.javadocHtmlTagBuffers = new StringBuffer[text.depth+6], 0, length);
	    }
	    StringBuffer buffer = this.javadocHtmlTagBuffers[text.depth];
	    if (buffer == null) {
	    	buffer = new StringBuffer();
	    	this.javadocHtmlTagBuffers[text.depth] = buffer;
	    } else {
	    	buffer.setLength(0);
	    }

	    // New line will be added before next node
	    int max = text.separatorsPtr;
		int linesAfter = 0;
		int previousEnd = -1;
	    boolean isHtmlBreakTag = htmlTagID == JAVADOC_SINGLE_BREAK_TAG_ID;
		boolean isHtmlSeparatorTag = htmlTagID == JAVADOC_SEPARATOR_TAGS_ID;
		if (isHtmlBreakTag) {
			return 1;
		}

		// Iterate on text line separators
		boolean isCode = htmlTagID == JAVADOC_CODE_TAGS_ID;
		for (int idx=0, ptr=0; idx<=max || (text.htmlNodesPtr != -1 && ptr <= text.htmlNodesPtr); idx++) {

			// append text to buffer realigning with the line length
			int end = (idx > max) ? text.sourceEnd : (int) (text.separators[idx] >>> 32);
			int nodeKind = 0; // text break
			if (text.htmlNodesPtr >= 0 && ptr <= text.htmlNodesPtr && end > text.htmlNodes[ptr].sourceStart) {
				FormatJavadocNode node = text.htmlNodes[ptr];
				FormatJavadocText htmlTag = node.isText() ? (FormatJavadocText) node : null;
				int newLines = htmlTag == null ? 0 : htmlTag.linesBefore;
				if (linesAfter > newLines) {
					newLines = linesAfter;
					if (newLines > 1 && clearBlankLines) {
						if (idx < 2 || (text.htmlIndexes[idx-2] & JAVADOC_TAGS_ID_MASK) != JAVADOC_CODE_TAGS_ID) {
							newLines = 1;
						}
					}
				}
				if (textStart < previousEnd) {
					addReplaceEdit(textStart, previousEnd, buffer.toString());
				}
				boolean immutable = node.isImmutable();
				if (newLines == 0) {
					newLines = printJavadocBlockNodesNewLines(block, node, previousEnd);
				}
				int nodeStart = node.sourceStart;
				if (newLines > 0 || (idx > 1 && nodeStart > (previousEnd+1))) {
					printJavadocGapLines(previousEnd+1, nodeStart-1, newLines, clearBlankLines, false, null);
				}
				if (newLines > 0) textOnNewLine = true;
				buffer.setLength(0);
				if (node.isText()) {
					if (immutable) {
						// do not change immutable tags, just increment column
						if (textOnNewLine && this.commentIndentation != null) {
					    	addInsertEdit(node.sourceStart, this.commentIndentation);
					    	this.column += this.commentIndentation.length();
						}
						printJavadocImmutableText(htmlTag, block, textOnNewLine);
						this.column += getTextLength(block, htmlTag);
						linesAfter = 0;
					} else {
						linesAfter = printJavadocHtmlTag(htmlTag, block, textOnNewLine);
					}
					nodeKind = 1; // text
				} else {
					if (textOnNewLine && this.commentIndentation != null) {
				    	addInsertEdit(node.sourceStart, this.commentIndentation);
				    	this.column += this.commentIndentation.length();
					}
					printJavadocBlock((FormatJavadocBlock)node);
					linesAfter = 0;
					nodeKind = 2; // block
				}
				textStart = node.sourceEnd+1;
				ptr++;
				if (idx > max)  {
					return linesAfter;
				}
			} else {
				if (idx > 0 && linesAfter > 0) {
					printJavadocGapLines(previousEnd+1, nextStart-1, linesAfter, clearBlankLines, false, buffer);
					textOnNewLine = true;
				}
				boolean needIndentation = textOnNewLine;
				if (idx > 0) {
					if (!needIndentation && text.isTextAfterHtmlSeparatorTag(idx-1)) {
						needIndentation = true;
					}
				}
				this.needSpace = idx > 1 && (previousEnd+1) < nextStart; // There's no space between text and html tag or inline block => do not insert space a the beginning of the text
				printJavadocTextLine(buffer, nextStart, end, block, idx==0, needIndentation, idx==0/* opening html tag?*/ || text.htmlIndexes[idx-1] != -1);
				linesAfter = 0;
			    if (idx==0) {
			    	if (isHtmlSeparatorTag) {
				    	linesAfter = 1;
				    }
				} else if (text.htmlIndexes[idx-1] == JAVADOC_SINGLE_BREAK_TAG_ID) {
			    	linesAfter = 1;
			    }
			}

			// Replace with current buffer if there are several empty lines between text lines
			nextStart = (int) text.separators[idx];
			int endLine = Util.getLineNumber(end, this.lineEnds, startLine-1, this.maxLines);
			startLine = Util.getLineNumber(nextStart, this.lineEnds, endLine-1, this.maxLines);
			int linesGap = startLine - endLine;
			if (linesGap > 0) {
				if (clearBlankLines) {
					// keep previously computed lines after
				} else {
					if (idx==0 || linesGap > 1 || (idx < max && nodeKind==1 && (text.htmlIndexes[idx-1] & JAVADOC_TAGS_ID_MASK) != JAVADOC_IMMUTABLE_TAGS_ID)) {
						if (linesAfter < linesGap) {
							linesAfter = linesGap;
						}
					}
				}
			}
			textOnNewLine = linesAfter > 0;

			// print <pre> tag
			if (isCode) {
    			int codeEnd = (int) (text.separators[max] >>> 32);
    			if (codeEnd > end) {
    				if (this.formatter.preferences.comment_format_source) {
						if (textStart < end) addReplaceEdit(textStart, end, buffer.toString());
						// See whether there's a space before the code
						if (linesGap > 0) {
							int lineStart = this.scanner.getLineStart(startLine);
							if (nextStart > lineStart) { // if code starts at the line, then no leading space is needed
								this.scanner.resetTo(lineStart, nextStart-1);
								try {
									int token = this.scanner.getNextToken();
									if (token == TerminalTokens.TokenNameWHITESPACE) {
										// skip indentation
										token = this.scanner.getNextToken();
									}
									if (token == TerminalTokens.TokenNameMULTIPLY) {
										nextStart = this.scanner.currentPosition;
									}
								}
								catch (InvalidInputException iie) {
									// skip
								}
							}
						}
						// Format gap lines before code
						int newLines = linesGap;
						if (newLines == 0) newLines=1;
						this.needSpace = false;
						printJavadocGapLines(end+1, nextStart-1, newLines, false/* clear first blank lines inside <pre> tag as done by old formatter */, false, null);
						// Format the code
						printCodeSnippet(nextStart, codeEnd, linesGap);
						// Format the gap lines after the code
						nextStart = (int) text.separators[max];
	    				printJavadocGapLines(codeEnd+1, nextStart-1, 1, false/* clear blank lines inside <pre> tag as done by old formatter */, false, null);
	    				return 2;
    				}
    			} else {
					nextStart = (int) text.separators[max];
					if ((nextStart-1) > (end+1)) {
						int line1 = Util.getLineNumber(end+1, this.lineEnds, startLine-1, this.maxLines);
						int line2 = Util.getLineNumber(nextStart-1, this.lineEnds, line1-1, this.maxLines);
	    				int gapLines = line2-line1-1;
						printJavadocGapLines(end+1, nextStart-1, gapLines, false/* never clear blank lines inside <pre> tag*/, false, null);
						if (gapLines > 0) textOnNewLine = true;
					}
    			}
				return 1;
			}

			// store previous end
			previousEnd = end;
		}

		// Insert last gap
	    boolean closingTag = isHtmlBreakTag || (text.htmlIndexes != null && (text.htmlIndexes[max] & JAVADOC_TAGS_ID_MASK) == htmlTagID);
		boolean isValidHtmlSeparatorTag = max > 0 && isHtmlSeparatorTag && closingTag;
		if (previousEnd != -1) {
		    if (isValidHtmlSeparatorTag) {
				if (linesAfter == 0) linesAfter = 1;
			}
			if (linesAfter > 0) {
				printJavadocGapLines(previousEnd+1, nextStart-1, linesAfter, clearBlankLines, false, buffer);
				textOnNewLine = linesAfter > 0;
			}
		}

	    // Print closing tag
		boolean needIndentation = textOnNewLine;
		if (!needIndentation && !isHtmlBreakTag && text.htmlIndexes != null && text.isTextAfterHtmlSeparatorTag(max)) {
			needIndentation = true;
		}
		this.needSpace = !closingTag && max > 0 // not a single or not closed tag (e.g. <br>)
			&& (previousEnd+1) < nextStart; // There's no space between text and html tag or inline block => do not insert space a the beginning of the text
		printJavadocTextLine(buffer, nextStart, text.sourceEnd, block, max <= 0, needIndentation, closingTag/* closing html tag*/);
		if (textStart < text.sourceEnd) {
			addReplaceEdit(textStart, text.sourceEnd, buffer.toString());
		}

		// Reset
		this.needSpace = false;
		this.scanner.resetTo(text.sourceEnd+1, this.scannerEndPosition - 1);

		// Return the new lines to insert after
	    return isValidHtmlSeparatorTag ? 1 : 0;
    }

	private void printJavadocNewLine(StringBuffer buffer) {
	    buffer.append(this.lineSeparator);
	    this.column = 1;
	    printIndentationIfNecessary(buffer);
	    buffer.append(BLOCK_LINE_PREFIX);
	    this.column += BLOCK_LINE_PREFIX_LENGTH;
	    this.line++;
	    this.lastNumberOfNewLines++;
	}

	private void printJavadocText(FormatJavadocText text, FormatJavadocBlock block, boolean textOnNewLine) {

		boolean clearBlankLines = this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment;
		boolean joinLines = this.formatter.preferences.join_lines_in_comments;
		this.javadocTextBuffer.setLength(0);
		int textStart = text.sourceStart;
		int nextStart = textStart;
		int startLine = Util.getLineNumber(textStart, this.lineEnds, 0, this.maxLines);

		// Iterate on text line separators
		for (int idx=0, max=text.separatorsPtr; idx<=max ; idx++) {

			// append text to buffer realigning with the line length
			int end = (int) (text.separators[idx] >>> 32);
			boolean needIndentation = textOnNewLine;
			if (idx > 0) {
				if (!needIndentation && text.isTextAfterHtmlSeparatorTag(idx-1)) {
					needIndentation = true;
				}
			}
			this.needSpace = idx > 0;
			printJavadocTextLine(this.javadocTextBuffer, nextStart, end, block, idx==0 || (!joinLines && textOnNewLine)/*first text?*/, needIndentation, false /*not an html tag*/);
			textOnNewLine = false;

			// Replace with current buffer if there are several empty lines between text lines
			nextStart = (int) text.separators[idx];
			if (!clearBlankLines || !joinLines) {
				int endLine = Util.getLineNumber(end, this.lineEnds, startLine-1, this.maxLines);
				startLine = Util.getLineNumber(nextStart, this.lineEnds, endLine-1, this.maxLines);
				int gapLine = endLine;
				if (joinLines) gapLine++; // if not preserving line break then gap must be at least of one line
				if (startLine > gapLine) {
					addReplaceEdit(textStart, end, this.javadocTextBuffer.toString());
					textStart = nextStart;
					this.javadocTextBuffer.setLength(0);
					int newLines = startLine - endLine;
					if (clearBlankLines) newLines = 1;
					printJavadocGapLines(end+1, nextStart-1, newLines, this.formatter.preferences.comment_clear_blank_lines_in_javadoc_comment, false, null);
					textOnNewLine = true;
				}
				else if (startLine > endLine) {
					textOnNewLine = !joinLines;
				}
			}
		}

		// Replace remaining line
		boolean needIndentation = textOnNewLine;
		this.needSpace = text.separatorsPtr >= 0;
		printJavadocTextLine(this.javadocTextBuffer, nextStart, text.sourceEnd, block, text.separatorsPtr==-1 /* first text?*/, needIndentation, false /*not an html tag*/);
		// TODO Bring back following optimization
		// if (lastNewLines != this.lastNumberOfNewLines || (this.column - currentColumn) != (text.sourceEnd - text.sourceStart + 1)) {
			addReplaceEdit(textStart, text.sourceEnd, this.javadocTextBuffer.toString());
		// }

		// Reset
		this.needSpace = false;
		this.scanner.resetTo(text.sourceEnd+1, this.scannerEndPosition - 1);
	}

	/*
	 * Returns whether the text has been modified or not.
	 */
	private void printJavadocTextLine(StringBuffer buffer, int textStart, int textEnd, FormatJavadocBlock block, boolean firstText, boolean needIndentation, boolean isHtmlTag) {

		boolean headerLine = block.isHeaderLine() && this.lastNumberOfNewLines == 0;

		// First we need to know what is the indentation
		this.javadocTokensBuffer.setLength(0);
		int firstColumn = 1 + this.indentationLevel + BLOCK_LINE_PREFIX_LENGTH;
		int maxColumn = this.formatter.preferences.comment_line_length + 1;
		if (headerLine) {
			firstColumn++;
			maxColumn++;
		}
		if (needIndentation && this.commentIndentation != null) {
			buffer.append(this.commentIndentation);
	    	this.column += this.commentIndentation.length();
	    	firstColumn += this.commentIndentation.length();
		}
		if (this.column < firstColumn) {
			this.column = firstColumn;
		}

		// Scan the text token per token to compact it and size it the max line length
		String newLineString = null;
		try {
			this.scanner.resetTo(textStart, textEnd);
			this.scanner.skipComments = true;
			int previousToken = -1;
			boolean textOnNewLine = needIndentation;

			// Consume text token per token
    		while (!this.scanner.atEnd()) {
				int token;
				try {
					token = this.scanner.getNextToken();
				} catch (InvalidInputException iie) {
					token = consumeInvalidToken(textEnd);
				}
	    		int tokensBufferLength = this.javadocTokensBuffer.length();
    			int tokenStart = this.scanner.getCurrentTokenStartPosition();
	    		int tokenLength = (this.scanner.atEnd() ? this.scanner.eofPosition : this.scanner.currentPosition) - tokenStart;
				boolean insertSpace = (previousToken == TerminalTokens.TokenNameWHITESPACE || this.needSpace) && !textOnNewLine;
				String tokensBufferString = this.javadocTokensBuffer.toString().trim();
				switch (token) {
					case TerminalTokens.TokenNameWHITESPACE:
						if (tokensBufferLength > 0) {
							boolean shouldSplit = (this.column+tokensBufferLength) > maxColumn // the max length is reached
								&& !isHtmlTag
								&& (insertSpace || tokensBufferLength > 1) // allow to split at the beginning only when starting with an identifier or a token with a length > 1
								&& tokensBufferString.charAt(0) != '@'; // avoid to split just before a '@'
							if (shouldSplit) {
								this.lastNumberOfNewLines++;
								this.line++;
								if (newLineString == null) {
									this.tempBuffer.setLength(0);
									this.tempBuffer.append(this.lineSeparator);
							    	this.column = 1;
							    	printIndentationIfNecessary(this.tempBuffer);
						    		this.tempBuffer.append(BLOCK_LINE_PREFIX);
							    	this.column += BLOCK_LINE_PREFIX_LENGTH;
									if (this.commentIndentation != null) {
										this.tempBuffer.append(this.commentIndentation);
								    	this.column += this.commentIndentation.length();
									}
						    		firstColumn = this.column;
						    		newLineString = this.tempBuffer.toString();
								} else {
									this.column = firstColumn;
								}
								buffer.append(newLineString);
								buffer.append(tokensBufferString);
								this.column += tokensBufferString.length();
								if (headerLine) {
									firstColumn--;
									maxColumn--;
									headerLine = false;
								}
							} else {
								buffer.append(this.javadocTokensBuffer);
								this.column += tokensBufferLength;
							}
							this.javadocTokensBuffer.setLength(0);
						}
						textOnNewLine = false;
						previousToken = token;
						continue;
					case TerminalTokens.TokenNameCharacterLiteral:
						if (this.scanner.currentPosition > this.scanner.eofPosition) {
							this.scanner.resetTo(this.scanner.startPosition, textEnd);
							this.scanner.getNextChar();
							token = 1;
						}
						break;
				}
	    		int lastColumn = this.column + tokensBufferLength + tokenLength;
	    		if (insertSpace) lastColumn++;
				boolean shouldSplit = lastColumn > maxColumn // the max length is reached
					&& (!isHtmlTag || previousToken == -1) // not an html tag or just at the beginning of it
					&& token != TerminalTokens.TokenNameAT && (tokensBufferLength == 0 || this.javadocTokensBuffer.charAt(tokensBufferLength-1) != '@'); // avoid to split just before a '@'
				if (shouldSplit) {
					// not enough space on the line
					if ((tokensBufferLength > 0 || tokenLength < maxColumn) && !isHtmlTag && tokensBufferLength > 0 && (firstColumn+tokensBufferLength+tokenLength) >= maxColumn) {
						// there won't be enough room even if we break the line before the buffered tokens
						// So add the buffered tokens now
						buffer.append(this.javadocTokensBuffer);
						this.column += tokensBufferLength;
						this.javadocTokensBuffer.setLength(0);
						tokensBufferLength = 0;
						textOnNewLine = false;
					}
					if ((tokensBufferLength > 0 || /*(firstColumn+tokenLength) < maxColumn || (insertSpace &&*/ this.column > firstColumn) && (!textOnNewLine || !firstText)) {
						this.lastNumberOfNewLines++;
						this.line++;
						if (newLineString == null) {
							this.tempBuffer.setLength(0);
							this.tempBuffer.append(this.lineSeparator);
					    	this.column = 1;
					    	printIndentationIfNecessary(this.tempBuffer);
				    		this.tempBuffer.append(BLOCK_LINE_PREFIX);
					    	this.column += BLOCK_LINE_PREFIX_LENGTH;
							if (this.commentIndentation != null) {
								this.tempBuffer.append(this.commentIndentation);
						    	this.column += this.commentIndentation.length();
							}
				    		firstColumn = this.column;
				    		newLineString = this.tempBuffer.toString();
						} else {
							this.column = firstColumn;
						}
						buffer.append(newLineString);
					}
			    	if (tokensBufferLength > 0) {
			    		String tokensString = tokensBufferString;
						buffer.append(tokensString);
						this.column += tokensString.length();
						this.javadocTokensBuffer.setLength(0);
						tokensBufferLength = 0;
		    		}
					buffer.append(this.scanner.source, tokenStart, tokenLength);
					this.column += tokenLength;
					textOnNewLine = false;
					if (headerLine) {
						firstColumn--;
						maxColumn--;
						headerLine = false;
					}
    			} else {
					// append token to the line
		    		if (insertSpace) {
		    			this.javadocTokensBuffer.append(' ');
		    		}
					this.javadocTokensBuffer.append(this.scanner.source, tokenStart, tokenLength);
    			}
				previousToken = token;
    			this.needSpace = false;
    			if (headerLine && lastColumn == maxColumn && this.scanner.atEnd()) {
					this.lastNumberOfNewLines++;
					this.line++;
    			}
    		}
		}
		finally {
			this.scanner.skipComments = false;
			// Add remaining buffered tokens
			if (this.javadocTokensBuffer.length() > 0) {
				buffer.append(this.javadocTokensBuffer);
				this.column += this.javadocTokensBuffer.length();
			}
		}
    }

	public void printModifiers(Annotation[] annotations, ASTVisitor visitor) {
		printModifiers(annotations, visitor, ICodeFormatterConstants.ANNOTATION_UNSPECIFIED);
	}

	public void printModifiers(Annotation[] annotations, ASTVisitor visitor, int annotationSourceKind) {
		try {
			int annotationsLength = annotations != null ? annotations.length : 0;
			int annotationsIndex = 0;
			boolean isFirstModifier = true;
			int currentTokenStartPosition = this.scanner.currentPosition;
			boolean hasComment = false;
			boolean hasModifiers = false;
			while ((this.currentToken = this.scanner.getNextToken()) != TerminalTokens.TokenNameEOF) {
				int foundTaskCount = this.scanner.foundTaskCount;
				int tokenStartPosition = this.scanner.getCurrentTokenStartPosition();
				int tokenEndPosition = this.scanner.getCurrentTokenEndPosition();
				switch(this.currentToken) {
					case TerminalTokens.TokenNamepublic :
					case TerminalTokens.TokenNameprotected :
					case TerminalTokens.TokenNameprivate :
					case TerminalTokens.TokenNamestatic :
					case TerminalTokens.TokenNameabstract :
					case TerminalTokens.TokenNamefinal :
					case TerminalTokens.TokenNamenative :
					case TerminalTokens.TokenNamesynchronized :
					case TerminalTokens.TokenNametransient :
					case TerminalTokens.TokenNamevolatile :
					case TerminalTokens.TokenNamestrictfp :
						hasModifiers = true;
						print(this.scanner.currentPosition - this.scanner.startPosition, !isFirstModifier);
						isFirstModifier = false;
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameAT :
						hasModifiers = true;
						if (!isFirstModifier) {
							space();
						}
						this.scanner.resetTo(this.scanner.getCurrentTokenStartPosition(), this.scannerEndPosition - 1);
						if (annotationsIndex < annotationsLength) {
							boolean insertSpaceBeforeBrace = this.formatter.preferences.insert_space_before_opening_brace_in_array_initializer;
							this.formatter.preferences.insert_space_before_opening_brace_in_array_initializer = false;
							try {
								annotations[annotationsIndex++].traverse(visitor, (BlockScope) null);
							}
							finally {
								this.formatter.preferences.insert_space_before_opening_brace_in_array_initializer = insertSpaceBeforeBrace;
							}
							// https://bugs.eclipse.org/bugs/show_bug.cgi?id=122247
							boolean shouldAddNewLine = false;
							switch (annotationSourceKind) {
								case ICodeFormatterConstants.ANNOTATION_ON_TYPE :
									if (this.formatter.preferences.insert_new_line_after_annotation_on_type) {
										shouldAddNewLine = true;
									}
									break;
								case ICodeFormatterConstants.ANNOTATION_ON_FIELD :
									if (this.formatter.preferences.insert_new_line_after_annotation_on_field) {
										shouldAddNewLine = true;
									}
									break;
								case ICodeFormatterConstants.ANNOTATION_ON_METHOD :
									if (this.formatter.preferences.insert_new_line_after_annotation_on_method) {
										shouldAddNewLine = true;
									}
									break;
								case ICodeFormatterConstants.ANNOTATION_ON_PACKAGE :
									if (this.formatter.preferences.insert_new_line_after_annotation_on_package) {
										shouldAddNewLine = true;
									}
									break;
								case ICodeFormatterConstants.ANNOTATION_ON_PARAMETER :
									if (this.formatter.preferences.insert_new_line_after_annotation_on_parameter) {
										shouldAddNewLine = true;
									}
									break;
								case ICodeFormatterConstants.ANNOTATION_ON_LOCAL_VARIABLE :
									if (this.formatter.preferences.insert_new_line_after_annotation_on_local_variable) {
										shouldAddNewLine = true;
									}
									break;
								default:
									// do nothing when no annotation formatting option specified
							}
							if (shouldAddNewLine) {
								this.printNewLine();
							}
						} else {
							return;
						}
						isFirstModifier = false;
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameCOMMENT_BLOCK :
					case TerminalTokens.TokenNameCOMMENT_JAVADOC :
						if (this.useTags && this.editsEnabled) {
							boolean turnOff = false;
							if (foundTaskCount > 0) {
								setEditsEnabled(foundTaskCount);
								turnOff = true;
							} else if (this.tagsKind == this.currentToken
								&& CharOperation.equals(this.disablingTag, this.scanner.source, tokenStartPosition, tokenEndPosition+1)) {
    							this.editsEnabled = false;
								turnOff = true;
					    	}
							if (turnOff) {
								if (!this.editsEnabled && this.editsIndex > 1) {
									OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex-1];
									if (this.scanner.startPosition == currentEdit.offset+currentEdit.length) {
										printNewLinesBeforeDisablingComment();
									}
								}
							}
						}
						printBlockComment(this.currentToken == TerminalTokens.TokenNameCOMMENT_JAVADOC);
						if (this.useTags && !this.editsEnabled) {
							if (foundTaskCount > 0) {
								setEditsEnabled(foundTaskCount);
							} else if (this.tagsKind == this.currentToken) {
	    						this.editsEnabled = CharOperation.equals(this.enablingTag, this.scanner.source, tokenStartPosition, tokenEndPosition+1);
					    	}
						}
						currentTokenStartPosition = this.scanner.currentPosition;
						hasComment = true;
						break;
					case TerminalTokens.TokenNameCOMMENT_LINE :
						tokenEndPosition = -this.scanner.commentStops[this.scanner.commentPtr];
						if (this.useTags && this.editsEnabled) {
							boolean turnOff = false;
							if (foundTaskCount > 0) {
								setEditsEnabled(foundTaskCount);
								turnOff = true;
							} else if (this.tagsKind == this.currentToken
								&& CharOperation.equals(this.disablingTag, this.scanner.source, tokenStartPosition, tokenEndPosition)) {
    							this.editsEnabled = false;
								turnOff = true;
					    	}
							if (turnOff) {
								if (!this.editsEnabled && this.editsIndex > 1) {
									OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex-1];
									if (this.scanner.startPosition == currentEdit.offset+currentEdit.length) {
										printNewLinesBeforeDisablingComment();
									}
								}
							}
						}
						printLineComment();
						if (this.useTags && !this.editsEnabled) {
							if (foundTaskCount > 0) {
								setEditsEnabled(foundTaskCount);
							} else if (this.tagsKind == this.currentToken) {
	    						this.editsEnabled = CharOperation.equals(this.enablingTag, this.scanner.source, tokenStartPosition, tokenEndPosition);
					    	}
						}
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameWHITESPACE :
						addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
						int count = 0;
						char[] whiteSpaces = this.scanner.getCurrentTokenSource();
						for (int i = 0, max = whiteSpaces.length; i < max; i++) {
							switch(whiteSpaces[i]) {
								case '\r' :
									if ((i + 1) < max) {
										if (whiteSpaces[i + 1] == '\n') {
											i++;
										}
									}
									count++;
									break;
								case '\n' :
									count++;
							}
						}
						if (count >= 1 && hasComment) {
							printNewLine();
						}
						currentTokenStartPosition = this.scanner.currentPosition;
						hasComment = false;
						break;
					default:
						if (hasModifiers) {
							space();
						}
						// step back one token
						this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
						return;
				}
			}
		} catch (InvalidInputException e) {
			throw new AbortFormatting(e);
		}
	}

	public void printNewLine() {
		this.printNewLine(this.scanner.getCurrentTokenEndPosition() + 1);
	}

	public void printNewLine(int insertPosition) {
		if (this.nlsTagCounter > 0) {
			return;
		}
		if (this.lastNumberOfNewLines >= 1) {
			// ensure that the scribe is at the beginning of a new line
			// only if no specific indentation has been previously set
			if (!this.preserveLineBreakIndentation) {
				this.column = 1; 
			}
			this.preserveLineBreakIndentation = false;
			return;
		}
		addInsertEdit(insertPosition, this.lineSeparator);
		this.line++;
		this.lastNumberOfNewLines = 1;
		this.column = 1;
		this.needSpace = false;
		this.pendingSpace = false;
		this.preserveLineBreakIndentation = false;
		this.lastLineComment.contiguous = false;
	}

	/*
	 * Print the indentation of a disabling comment
	 */
	private void printNewLinesBeforeDisablingComment() {

		// Get the beginning of comment line
		int linePtr = Arrays.binarySearch(this.lineEnds, this.scanner.startPosition);
		if (linePtr < 0) {
			linePtr = -linePtr - 1;
		}
		int indentation = 0;
		int beginningOfLine = getLineEnd(linePtr)+1;
		if (beginningOfLine == -1) {
			beginningOfLine = 0;
		}
		
		// If the comment is in the middle of the line, then there's nothing to do
		OptimizedReplaceEdit currentEdit = this.edits[this.editsIndex-1];
		int offset = currentEdit.offset;
		if (offset >= beginningOfLine) return;

		// Compute the comment indentation
		int scannerStartPosition = this.scanner.startPosition;
		int scannerEofPosition = this.scanner.eofPosition;
		int scannerCurrentPosition = this.scanner.currentPosition;
		char scannerCurrentChar = this.scanner.currentCharacter;
		int length = currentEdit.length;
		this.scanner.resetTo(beginningOfLine, offset+length-1);
		try {
			while (!this.scanner.atEnd()) {
				char ch = (char) this.scanner.getNextChar();
				switch (ch) {
					case '\t' :
						if (this.tabLength != 0) {
							int reminder = indentation % this.tabLength;
							if (reminder == 0) {
								indentation += this.tabLength;
							} else {
								indentation = ((indentation / this.tabLength) + 1) * this.tabLength;
							}
						}
						break;
					case ' ':
						indentation++;
						break;
					default:
						// Should not happen as the offset of the edit is before the beginning of line
						return;
				}
			}
		
			// Split the existing edit to keep the change before the beginning of the last line
			// but change the indentation after. Note that at this stage, the add*Edit methods
			// cannot be longer used as the edits are disabled
			String indentationString;
			int currentIndentation = getCurrentIndentation(this.scanner.currentPosition);
			if (currentIndentation > 0 && this.indentationLevel > 0) {
				int col = this.column;
				this.tempBuffer.setLength(0);
				printIndentationIfNecessary(this.tempBuffer);
				indentationString = this.tempBuffer.toString();
				this.column = col;
			} else {
				indentationString = Util.EMPTY_STRING;
			}
			String replacement = currentEdit.replacement;
			if (replacement.length() == 0) {
				// previous edit was a delete, as we're sure to have a new line before
				// the comment, then the edit needs to be either replaced entirely with
				// the expected indentation
				this.edits[this.editsIndex-1] = new OptimizedReplaceEdit(beginningOfLine, offset+length-beginningOfLine, indentationString);
			} else {
				int idx = replacement.lastIndexOf(this.lineSeparator);
				if (idx >= 0) {
					// replace current edit if it contains a line separator
					int start = idx + this.lsLength;
					this.tempBuffer.setLength(0);
					this.tempBuffer.append(replacement.substring(0, start));
					if (indentationString != Util.EMPTY_STRING) {
						this.tempBuffer.append(indentationString);
					}
					this.edits[this.editsIndex-1] = new OptimizedReplaceEdit(offset, length, this.tempBuffer.toString());
				}
			}
		}
		finally {
			this.scanner.startPosition = scannerStartPosition;
			this.scanner.eofPosition = scannerEofPosition;
			this.scanner.currentPosition = scannerCurrentPosition;
			this.scanner.currentCharacter = scannerCurrentChar;
		}
	}

	/*
	 * Print new lines characters when the edits are disabled. In this case, only
	 * the line separator is replaced if necessary, the other white spaces are untouched.
	 */
	private boolean printNewLinesCharacters(int offset, int length) {
		boolean foundNewLine = false;
		int scannerStartPosition = this.scanner.startPosition;
		int scannerEofPosition = this.scanner.eofPosition;
		int scannerCurrentPosition = this.scanner.currentPosition;
		char scannerCurrentChar = this.scanner.currentCharacter;
		this.scanner.resetTo(offset, offset+length-1);
		try {
			while (!this.scanner.atEnd()) {
				int start = this.scanner.currentPosition;
				char ch = (char) this.scanner.getNextChar();
				boolean needReplace = ch != this.firstLS;
				switch (ch) {
					case '\r':
						if (this.scanner.atEnd()) break;
						ch = (char) this.scanner.getNextChar();
						if (ch != '\n') break;
						needReplace = needReplace || this.lsLength != 2;
						//$FALL-THROUGH$
					case '\n':
						if (needReplace) {
							if (this.editsIndex == 0 || this.edits[this.editsIndex-1].offset != start) {
								this.edits[this.editsIndex++] = new OptimizedReplaceEdit(start, this.scanner.currentPosition-start, this.lineSeparator);
							}
						}
						foundNewLine = true;
						break;
				}
			}
		}
		finally {
			this.scanner.startPosition = scannerStartPosition;
			this.scanner.eofPosition = scannerEofPosition;
			this.scanner.currentPosition = scannerCurrentPosition;
			this.scanner.currentCharacter = scannerCurrentChar;
		}
		return foundNewLine;		
	}

	public void printNextToken(int expectedTokenType){
		printNextToken(expectedTokenType, false);
	}

	public void printNextToken(int expectedTokenType, boolean considerSpaceIfAny) {
		printNextToken(expectedTokenType, considerSpaceIfAny, PRESERVE_EMPTY_LINES_KEEP_LAST_NEW_LINES_INDENTATION);
	}

	public void printNextToken(int expectedTokenType, boolean considerSpaceIfAny, int emptyLineRules) {
		// Set brace flag, it's useful for the scribe while preserving line breaks
		printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT, emptyLineRules);
		try {
			this.currentToken = this.scanner.getNextToken();
			if (expectedTokenType != this.currentToken) {
				throw new AbortFormatting("unexpected token type, expecting:"+expectedTokenType+", actual:"+this.currentToken);//$NON-NLS-1$//$NON-NLS-2$
			}
			print(this.scanner.currentPosition - this.scanner.startPosition, considerSpaceIfAny);
		} catch (InvalidInputException e) {
			throw new AbortFormatting(e);
		}
	}

	public void printNextToken(int[] expectedTokenTypes) {
		printNextToken(expectedTokenTypes, false);
	}

	public void printNextToken(int[] expectedTokenTypes, boolean considerSpaceIfAny){
		printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT);
		try {
			this.currentToken = this.scanner.getNextToken();
			if (Arrays.binarySearch(expectedTokenTypes, this.currentToken) < 0) {
				StringBuffer expectations = new StringBuffer(5);
				for (int i = 0; i < expectedTokenTypes.length; i++){
					if (i > 0) {
						expectations.append(',');
					}
					expectations.append(expectedTokenTypes[i]);
				}
				throw new AbortFormatting("unexpected token type, expecting:["+expectations.toString()+"], actual:"+this.currentToken);//$NON-NLS-1$//$NON-NLS-2$
			}
			print(this.scanner.currentPosition - this.scanner.startPosition, considerSpaceIfAny);
		} catch (InvalidInputException e) {
			throw new AbortFormatting(e);
		}
	}

	public void printArrayQualifiedReference(int numberOfTokens, int sourceEnd) {
		int currentTokenStartPosition = this.scanner.currentPosition;
		int numberOfIdentifiers = 0;
		try {
			do {
				printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT);
				switch(this.currentToken = this.scanner.getNextToken()) {
					case TerminalTokens.TokenNameEOF :
						return;
					case TerminalTokens.TokenNameWHITESPACE :
						addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameCOMMENT_BLOCK :
					case TerminalTokens.TokenNameCOMMENT_JAVADOC :
						printBlockComment(false);
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameCOMMENT_LINE :
						printLineComment();
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameIdentifier :
						print(this.scanner.currentPosition - this.scanner.startPosition, false);
						currentTokenStartPosition = this.scanner.currentPosition;
						if (++ numberOfIdentifiers == numberOfTokens) {
							this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
							return;
						}
						break;
					case TerminalTokens.TokenNameDOT :
						print(this.scanner.currentPosition - this.scanner.startPosition, false);
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameRPAREN:
						currentTokenStartPosition = this.scanner.startPosition;
						// $FALL-THROUGH$ - fall through default case...
					default:
						this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
						return;
				}
			} while (this.scanner.currentPosition <= sourceEnd);
		} catch(InvalidInputException e) {
			throw new AbortFormatting(e);
		}
	}

	public void printQualifiedReference(int sourceEnd, boolean expectParenthesis) {
		int currentTokenStartPosition = this.scanner.currentPosition;
		try {
			do {
				printComment(CodeFormatter.K_UNKNOWN, NO_TRAILING_COMMENT);
				switch(this.currentToken = this.scanner.getNextToken()) {
					case TerminalTokens.TokenNameEOF :
						return;
					case TerminalTokens.TokenNameWHITESPACE :
						addDeleteEdit(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition());
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameCOMMENT_BLOCK :
					case TerminalTokens.TokenNameCOMMENT_JAVADOC :
						printBlockComment(false);
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameCOMMENT_LINE :
						printLineComment();
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameIdentifier :
					case TerminalTokens.TokenNameDOT :
						print(this.scanner.currentPosition - this.scanner.startPosition, false);
						currentTokenStartPosition = this.scanner.currentPosition;
						break;
					case TerminalTokens.TokenNameRPAREN:
						if (expectParenthesis) {
							currentTokenStartPosition = this.scanner.startPosition;
						}
						// $FALL-THROUGH$ - fall through default case...
					default:
						this.scanner.resetTo(currentTokenStartPosition, this.scannerEndPosition - 1);
						return;
				}
			} while (this.scanner.currentPosition <= sourceEnd);
		} catch(InvalidInputException e) {
			throw new AbortFormatting(e);
		}
	}

	private void printRule(StringBuffer stringBuffer) {
		// only called if this.tabLength > 0
		for (int i = 0; i < this.pageWidth; i++){
			if ((i % this.tabLength) == 0) {
				stringBuffer.append('+');
			} else {
				stringBuffer.append('-');
			}
		}
		stringBuffer.append(this.lineSeparator);

		for (int i = 0; i < (this.pageWidth / this.tabLength); i++) {
			stringBuffer.append(i);
			stringBuffer.append('\t');
		}
	}

	void redoAlignment(AlignmentException e){
		if (e.relativeDepth > 0) { // if exception targets a distinct context
			e.relativeDepth--; // record fact that current context got traversed
			this.currentAlignment = this.currentAlignment.enclosing; // pop currentLocation
			throw e; // rethrow
		}
		// reset scribe/scanner to restart at this given location
		resetAt(this.currentAlignment.location);
		this.scanner.resetTo(this.currentAlignment.location.inputOffset, this.scanner.eofPosition - 1);
		// clean alignment chunkKind so it will think it is a new chunk again
		this.currentAlignment.chunkKind = 0;
	}

	void redoMemberAlignment(AlignmentException e){
		// reset scribe/scanner to restart at this given location
		resetAt(this.memberAlignment.location);
		this.scanner.resetTo(this.memberAlignment.location.inputOffset, this.scanner.eofPosition - 1);
		// clean alignment chunkKind so it will think it is a new chunk again
		this.memberAlignment.chunkKind = 0;
	}

	public void reset() {
		this.checkLineWrapping = true;
		this.line = 0;
		this.column = 1;
		this.editsIndex = 0;
		this.nlsTagCounter = 0;
	}

	private void resetAt(Location location) {
		this.line = location.outputLine;
		this.column = location.outputColumn;
		this.indentationLevel = location.outputIndentationLevel;
		this.numberOfIndentations = location.numberOfIndentations;
		this.lastNumberOfNewLines = location.lastNumberOfNewLines;
		this.needSpace = location.needSpace;
		this.pendingSpace = location.pendingSpace;
		this.editsIndex = location.editsIndex;
		this.nlsTagCounter = location.nlsTagCounter;
		if (this.editsIndex > 0) {
			this.edits[this.editsIndex - 1] = location.textEdit;
		}
		this.formatter.lastLocalDeclarationSourceStart = location.lastLocalDeclarationSourceStart;
	}

	/**
	 * @param compilationUnitSource
	 */
	public void resetScanner(char[] compilationUnitSource) {
		this.scanner.setSource(compilationUnitSource);
		this.scannerEndPosition = compilationUnitSource.length;
		this.scanner.resetTo(0, this.scannerEndPosition - 1);
		this.edits = new OptimizedReplaceEdit[INITIAL_SIZE];
		this.maxLines = this.lineEnds == null ? -1 : this.lineEnds.length - 1;
		this.scanner.lineEnds = this.lineEnds;
		this.scanner.linePtr = this.maxLines;
		initFormatterCommentParser();
	}

	private void resize() {
		System.arraycopy(this.edits, 0, (this.edits = new OptimizedReplaceEdit[this.editsIndex * 2]), 0, this.editsIndex);
	}

	private void setCommentIndentation(int commentIndentationLevel) {
		if (commentIndentationLevel == 0) {
		    this.commentIndentation = null;
		} else {
			int length = COMMENT_INDENTATIONS.length;
			if (commentIndentationLevel > length) {
				System.arraycopy(COMMENT_INDENTATIONS, 0, COMMENT_INDENTATIONS = new String[commentIndentationLevel+10], 0, length);
			}
			this.commentIndentation = COMMENT_INDENTATIONS[commentIndentationLevel-1];
			if (this.commentIndentation == null) {
				this.tempBuffer.setLength(0);
				for (int i=0; i<commentIndentationLevel; i++) {
					this.tempBuffer.append(' ');
				}
				this.commentIndentation = this.tempBuffer.toString();
				COMMENT_INDENTATIONS[commentIndentationLevel-1] = this.commentIndentation;
			}
		}
	}

	/*
	 * Look for the tags identified by the scanner to see whether some of them
	 * may change the status of the edition for the formatter.
	 * Do not return as soon as a match is found, as there may have several
	 * disabling/enabling tags in a comment, hence the last one will be the one really
	 * changing the formatter behavior...
	 */
	private void setEditsEnabled(int count) {
		for (int i=0; i<count; i++) {
			if (this.disablingTag != null && CharOperation.equals(this.scanner.foundTaskTags[i], this.disablingTag)) {
				this.editsEnabled = false;
			}
			if (this.enablingTag != null && CharOperation.equals(this.scanner.foundTaskTags[i], this.enablingTag)) {
				this.editsEnabled = true;
			}
		}
	}

	void setIncludeComments(boolean on) {
		if (on) {
			this.formatComments |= CodeFormatter.F_INCLUDE_COMMENTS;
		} else {
			this.formatComments &= ~CodeFormatter.F_INCLUDE_COMMENTS;
		}
	}

	void setHeaderComment(int position) {
		this.headerEndPosition = position;
	}

	public void space() {
		if (!this.needSpace) return;
		this.lastNumberOfNewLines = 0;
		this.pendingSpace = true;
		this.column++;
		this.needSpace = false;
	}

	public String toString() {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer
			.append("(page width = " + this.pageWidth + ") - (tabChar = ");//$NON-NLS-1$//$NON-NLS-2$
		switch(this.tabChar) {
			case DefaultCodeFormatterOptions.TAB :
				 stringBuffer.append("TAB");//$NON-NLS-1$
				 break;
			case DefaultCodeFormatterOptions.SPACE :
				 stringBuffer.append("SPACE");//$NON-NLS-1$
				 break;
			default :
				 stringBuffer.append("MIXED");//$NON-NLS-1$
		}
		stringBuffer
			.append(") - (tabSize = " + this.tabLength + ")")//$NON-NLS-1$//$NON-NLS-2$
			.append(this.lineSeparator)
			.append("(line = " + this.line + ") - (column = " + this.column + ") - (identationLevel = " + this.indentationLevel + ")")	//$NON-NLS-1$	//$NON-NLS-2$	//$NON-NLS-3$	//$NON-NLS-4$
			.append(this.lineSeparator)
			.append("(needSpace = " + this.needSpace + ") - (lastNumberOfNewLines = " + this.lastNumberOfNewLines + ") - (checkLineWrapping = " + this.checkLineWrapping + ")")	//$NON-NLS-1$	//$NON-NLS-2$	//$NON-NLS-3$	//$NON-NLS-4$
			.append(this.lineSeparator)
			.append("==================================================================================")	//$NON-NLS-1$
			.append(this.lineSeparator);
		if (this.tabLength > 0) {
			printRule(stringBuffer);
		}
		return stringBuffer.toString();
	}

	public void unIndent() {
		this.indentationLevel -= this.indentationSize;
		this.numberOfIndentations--;
	}
}
