/**
   * Computes and returns the indentation for a javadoc line. The line must be inside a javadoc
   * comment.
   *
   * @param document the document
   * @param line the line in document
   * @param scanner the scanner
   * @param partition the comment partition
   * @return the indent, or <code>null</code> if not computable
   * @throws BadLocationException
   */
  private static String computeJavadocIndent(
      IDocument document, int line, JavaHeuristicScanner scanner, ITypedRegion partition)
      throws BadLocationException {
    if (line == 0) // impossible - the first line is never inside a javadoc comment
    return null;

    // don't make any assumptions if the line does not start with \s*\* - it might be
    // commented out code, for which we don't want to change the indent
    final IRegion lineInfo = document.getLineInformation(line);
    final int lineStart = lineInfo.getOffset();
    final int lineLength = lineInfo.getLength();
    final int lineEnd = lineStart + lineLength;
    int nonWS = scanner.findNonWhitespaceForwardInAnyPartition(lineStart, lineEnd);
    if (nonWS == JavaHeuristicScanner.NOT_FOUND || document.getChar(nonWS) != '*') {
      if (nonWS == JavaHeuristicScanner.NOT_FOUND) return document.get(lineStart, lineLength);
      return document.get(lineStart, nonWS - lineStart);
    }

    // take the indent from the previous line and reuse
    IRegion previousLine = document.getLineInformation(line - 1);
    int previousLineStart = previousLine.getOffset();
    int previousLineLength = previousLine.getLength();
    int previousLineEnd = previousLineStart + previousLineLength;

    StringBuffer buf = new StringBuffer();
    int previousLineNonWS =
        scanner.findNonWhitespaceForwardInAnyPartition(previousLineStart, previousLineEnd);
    if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND
        || document.getChar(previousLineNonWS) != '*') {
      // align with the comment start if the previous line is not an asterix line
      previousLine = document.getLineInformationOfOffset(partition.getOffset());
      previousLineStart = previousLine.getOffset();
      previousLineLength = previousLine.getLength();
      previousLineEnd = previousLineStart + previousLineLength;
      previousLineNonWS =
          scanner.findNonWhitespaceForwardInAnyPartition(previousLineStart, previousLineEnd);
      if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND) previousLineNonWS = previousLineEnd;

      // add the initial space
      // TODO this may be controlled by a formatter preference in the future
      buf.append(' ');
    }

    String indentation = document.get(previousLineStart, previousLineNonWS - previousLineStart);
    buf.insert(0, indentation);
    return buf.toString();
  }
  /**
   * Indents a single line using the java heuristic scanner. Javadoc and multi line comments are
   * indented as specified by the <code>JavaDocAutoIndentStrategy</code>.
   *
   * @param document the document
   * @param line the line to be indented
   * @param indenter the java indenter
   * @param scanner the heuristic scanner
   * @param commentLines the indent token comment booleans
   * @param lineIndex the zero-based line index
   * @return <code>true</code> if the document was modified, <code>false</code> if not
   * @throws BadLocationException if the document got changed concurrently
   */
  private static boolean indentLine(
      IDocument document,
      int line,
      JavaIndenter indenter,
      JavaHeuristicScanner scanner,
      boolean[] commentLines,
      int lineIndex,
      int tabSize)
      throws BadLocationException {
    IRegion currentLine = document.getLineInformation(line);
    final int offset = currentLine.getOffset();
    int wsStart =
        offset; // where we start searching for non-WS; after the "//" in single line comments

    String indent = null;
    if (offset < document.getLength()) {
      ITypedRegion partition =
          TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, offset, true);
      ITypedRegion startingPartition =
          TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, offset, false);
      String type = partition.getType();
      if (type.equals(IJavaPartitions.JAVA_DOC)
          || type.equals(IJavaPartitions.JAVA_MULTI_LINE_COMMENT)) {
        indent = computeJavadocIndent(document, line, scanner, startingPartition);
      } else if (!commentLines[lineIndex]
          && startingPartition.getOffset() == offset
          && startingPartition.getType().equals(IJavaPartitions.JAVA_SINGLE_LINE_COMMENT)) {
        return false;
      }
    }

    // standard java indentation
    if (indent == null) {
      StringBuffer computed = indenter.computeIndentation(offset);
      if (computed != null) indent = computed.toString();
      else indent = new String();
    }

    // change document:
    // get current white space
    int lineLength = currentLine.getLength();
    int end = scanner.findNonWhitespaceForwardInAnyPartition(wsStart, offset + lineLength);
    if (end == JavaHeuristicScanner.NOT_FOUND) end = offset + lineLength;
    int length = end - offset;
    String currentIndent = document.get(offset, length);

    // memorize the fact that a line is a single line comment (but not at column 0) and should be
    // treated like code
    // as opposed to commented out code, which should keep its slashes at column 0
    if (length > 0) {
      ITypedRegion partition =
          TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, end, false);
      if (partition.getOffset() == end
          && IJavaPartitions.JAVA_SINGLE_LINE_COMMENT.equals(partition.getType())) {
        commentLines[lineIndex] = true;
      }
    }

    // only change the document if it is a real change
    if (!indent.equals(currentIndent)) {
      document.replace(offset, length, indent);
      return true;
    }

    return false;
  }