/**
   * Does the comment range contain no letters and digits?
   *
   * @param range the comment range to text
   * @return <code>true</code> iff the comment range contains no letters and digits, <code>false
   *     </code> otherwise
   */
  protected final boolean isNonAlphaNumeric(final CommentRange range) {

    final String token = getText(range.getOffset(), range.getLength());

    for (int index = 0; index < token.length(); index++) {
      if (ScannerHelper.isLetterOrDigit(token.charAt(index))) return false;
    }
    return true;
  }
 /**
  * Can the comment range be appended to the comment line?
  *
  * @param line comment line where to append the comment range
  * @param previous comment range which is the predecessor of the current comment range
  * @param next comment range to test whether it can be appended to the comment line
  * @param index amount of space in the comment line used by already inserted comment ranges
  * @param width the maximal width of text in this comment region measured in average character
  *     widths
  * @return <code>true</code> iff the comment range can be added to the line, <code>false</code>
  *     otherwise
  */
 protected boolean canAppend(
     final CommentLine line,
     final CommentRange previous,
     final CommentRange next,
     final int index,
     final int width) {
   return index == 0 || index + next.getLength() <= width;
 }
  /**
   * Wraps the comment ranges in this comment region into comment lines.
   *
   * @param width the maximal width of text in this comment region measured in average character
   *     widths
   */
  protected void wrapRegion(final int width) {

    fLines.clear();

    int index = 0;
    boolean adapted = false;

    CommentLine successor = null;
    CommentLine predecessor = null;

    CommentRange previous = null;
    CommentRange next = null;

    while (!fRanges.isEmpty()) {

      index = 0;
      adapted = false;

      predecessor = successor;
      successor = createLine();
      fLines.add(successor);

      while (!fRanges.isEmpty()) {
        next = (CommentRange) fRanges.getFirst();

        if (canAppend(successor, previous, next, index, width)) {

          if (!adapted && predecessor != null) {

            successor.adapt(predecessor);
            adapted = true;
          }

          fRanges.removeFirst();
          successor.append(next);

          index += (next.getLength() + 1);
          previous = next;
        } else break;
      }
    }
  }
  /*
   * @see org.eclipse.wst.jsdt.internal.corext.text.comment.CommentLine#append(org.eclipse.wst.jsdt.internal.corext.text.comment.CommentRange)
   */
  protected void append(final CommentRange range) {

    final MultiCommentRegion parent = (MultiCommentRegion) getParent();

    if (range.hasAttribute(COMMENT_PARAMETER)) setAttribute(COMMENT_PARAMETER);
    else if (range.hasAttribute(COMMENT_ROOT)) setAttribute(COMMENT_ROOT);
    else if (range.hasAttribute(COMMENT_BLANKLINE)) setAttribute(COMMENT_BLANKLINE);

    final int ranges = getSize();
    if (ranges == 1) {

      if (parent.isIndentRoots()) {

        final CommentRange first = getFirst();
        final String common =
            parent.getText(first.getOffset(), first.getLength())
                + CommentRegion.COMMENT_RANGE_DELIMITER;

        if (hasAttribute(COMMENT_ROOT)) fReferenceIndentation = common;
        else if (hasAttribute(COMMENT_PARAMETER)) {
          if (parent.isIndentDescriptions()) fReferenceIndentation = "\t" + common; // $NON-NLS-1$
          else fReferenceIndentation = common;
        }
      }
    }
    super.append(range);
  }
  /*
   * @see org.eclipse.wst.jsdt.internal.corext.text.comment.CommentLine#tokenizeLine(int)
   */
  protected void tokenizeLine(int line) {

    int offset = 0;
    int index = offset;

    final CommentRegion parent = getParent();
    final CommentRange range = getFirst();
    final int begin = range.getOffset();

    final String content = parent.getText(begin, range.getLength());
    final int length = content.length();

    while (offset < length && ScannerHelper.isWhitespace(content.charAt(offset))) offset++;

    CommentRange result = null;
    if (offset >= length && !parent.isClearLines() && (line > 0 && line < parent.getSize() - 1)) {

      result = new CommentRange(begin, 0);
      result.setAttribute(COMMENT_BLANKLINE);
      result.setAttribute(COMMENT_FIRST_TOKEN);

      parent.append(result);
    }

    int attribute = COMMENT_FIRST_TOKEN | COMMENT_STARTS_WITH_RANGE_DELIMITER;
    while (offset < length) {

      while (offset < length && ScannerHelper.isWhitespace(content.charAt(offset))) {
        offset++;
        attribute |= COMMENT_STARTS_WITH_RANGE_DELIMITER;
      }

      index = offset;

      if (index < length) {

        if (content.charAt(index) == HTML_TAG_PREFIX) {

          // in order to avoid recognizing any < in a comment, even those which are part of e.g.
          // java source code, we validate the tag content to be one of the recognized
          // tags (structural, breaks, pre, code).
          int tag = ++index;
          while (index < length
              && content.charAt(index) != HTML_TAG_POSTFIX
              && content.charAt(index) != HTML_TAG_PREFIX) index++;

          if (index < length
              && content.charAt(index) == HTML_TAG_POSTFIX
              && isValidTag(content.substring(tag, index))) {
            index++;
            attribute |= COMMENT_HTML; // only set html attribute if postfix found
          } else {
            // no tag - do the usual thing from the original offset
            index = tag;
            while (index < length
                && !ScannerHelper.isWhitespace(content.charAt(index))
                && content.charAt(index) != HTML_TAG_PREFIX
                && !content.startsWith(LINK_TAG_PREFIX_STRING, index)) index++;
          }

        } else if (content.startsWith(LINK_TAG_PREFIX_STRING, index)) {

          while (index < length && content.charAt(index) != LINK_TAG_POSTFIX) index++;

          if (index < length && content.charAt(index) == LINK_TAG_POSTFIX) index++;

          attribute |= COMMENT_OPEN | COMMENT_CLOSE;

        } else {

          while (index < length
              && !ScannerHelper.isWhitespace(content.charAt(index))
              && content.charAt(index) != HTML_TAG_PREFIX
              && !content.startsWith(LINK_TAG_PREFIX_STRING, index)) index++;
        }
      }

      if (index - offset > 0) {

        result = new CommentRange(begin + offset, index - offset);
        result.setAttribute(attribute);

        parent.append(result);
        offset = index;
      }

      attribute = 0;
    }
  }
  /*
   * @see org.eclipse.wst.jsdt.internal.corext.text.comment.CommentLine#scanLine(int)
   */
  protected void scanLine(final int line) {

    final CommentRegion parent = getParent();
    final String start = getStartingPrefix().trim();
    final String end = getEndingPrefix().trim();
    final String content = getContentPrefix().trim();

    final int lines = parent.getSize();
    final CommentRange range = getFirst();

    int offset = 0;
    int postfix = 0;

    String text = parent.getText(range.getOffset(), range.getLength());
    if (line == 0) {

      offset = text.indexOf(start);
      if (offset >= 0 && text.substring(0, offset).trim().length() != 0) offset = -1;

      if (offset >= 0) {

        offset += start.length();
        range.trimBegin(offset);

        postfix = text.lastIndexOf(end);
        if (postfix >= 0 && text.substring(postfix + end.length()).trim().length() != 0)
          postfix = -1;

        if (postfix >= offset)
          // comment ends on same line
          range.setLength(postfix - offset);
        else {
          postfix = text.lastIndexOf(content);
          if (postfix >= 0 && text.substring(postfix + content.length()).trim().length() != 0)
            postfix = -1;

          if (postfix >= offset) {

            range.setLength(postfix - offset);
            parent.setBorder(BORDER_UPPER);

            if (postfix > offset) {

              text = parent.getText(range.getOffset(), range.getLength());
              final IRegion region = trimLine(text, content);

              range.move(region.getOffset());
              range.setLength(region.getLength());
            }
          }
        }
      }
    } else if (line == lines - 1) {

      offset = text.indexOf(content);
      if (offset >= 0 && text.substring(0, offset).trim().length() != 0) offset = -1;
      postfix = text.lastIndexOf(end);
      if (postfix >= 0 && text.substring(postfix + end.length()).trim().length() != 0) postfix = -1;

      if (offset >= 0 && offset == postfix)
        // no content on line, only the comment postfix
        range.setLength(0);
      else {
        if (offset >= 0)
          // omit the content prefix
          range.trimBegin(offset + content.length());

        if (postfix >= 0)
          // omit the comment postfix
          range.trimEnd(-end.length());

        text = parent.getText(range.getOffset(), range.getLength());
        final IRegion region = trimLine(text, content);
        if (region.getOffset() != 0 || region.getLength() != text.length()) {

          range.move(region.getOffset());
          range.setLength(region.getLength());

          parent.setBorder(BORDER_UPPER);
          parent.setBorder(BORDER_LOWER);
        }
      }
    } else {

      offset = text.indexOf(content);
      if (offset >= 0 && text.substring(0, offset).trim().length() != 0) offset = -1;

      if (offset >= 0) {

        offset += content.length();
        range.trimBegin(offset);
      }
    }
  }