Package org.eclipse.php.internal.core.ast.nodes

Source Code of org.eclipse.php.internal.core.ast.nodes.DefaultCommentMapper$CommentMapperVisitor

/*******************************************************************************
* Copyright (c) 2009 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
*     Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.core.ast.nodes;

import java.io.IOException;
import java.io.Reader;
import java.util.Collection;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.php.internal.core.ast.scanner.AstLexer;
import org.eclipse.php.internal.core.ast.visitor.AbstractVisitor;
import org.eclipse.php.internal.core.ast.visitor.ApplyAll;

/**
* Internal class for associating comments with AST nodes.
*
* @since 3.0
*/
public class DefaultCommentMapper {
  Comment[] comments;
  AstLexer scanner;

  // extended nodes storage
  int leadingPtr;
  ASTNode[] leadingNodes;
  long[] leadingIndexes;
  int trailingPtr, lastTrailingPtr;
  ASTNode[] trailingNodes;
  long[] trailingIndexes;
  private IDocument document;
  static final int STORAGE_INCREMENT = 16;

  /**
   * @param table
   *            the given table of comments
   */
  DefaultCommentMapper(Comment[] table) {
    this.comments = table;
  }

  boolean hasSameTable(Comment[] table) {
    return this.comments == table;
  }

  /**
   * Get comment of the list which includes a given position
   *
   * @param position
   *            The position belonging to the looked up comment
   * @return comment which includes the given position or null if none was
   *         found
   */
  Comment getComment(int position) {

    if (this.comments == null) {
      return null;
    }
    int size = this.comments.length;
    if (size == 0) {
      return null;
    }
    int index = getCommentIndex(0, position, 0);
    if (index < 0) {
      return null;
    }
    return this.comments[index];
  }

  /*
   * Get the index of comment which contains given position. If there's no
   * matching comment, then return depends on exact parameter: = 0: return -1
   * < 0: return index of the comment before the given position > 0: return
   * index of the comment after the given position
   */
  private int getCommentIndex(int start, int position, int exact) {
    if (position == 0) {
      if (this.comments.length > 0 && this.comments[0].getStart() == 0) {
        return 0;
      }
      return -1;
    }
    int bottom = start, top = this.comments.length - 1;
    int i = 0, index = -1;
    Comment comment = null;
    while (bottom <= top) {
      i = bottom + (top - bottom) / 2;
      comment = this.comments[i];
      int commentStart = comment.getStart();
      if (position < commentStart) {
        top = i - 1;
      } else if (position >= (commentStart + comment.getLength())) {
        bottom = i + 1;
      } else {
        index = i;
        break;
      }
    }
    if (index < 0 && exact != 0) {
      comment = this.comments[i];
      if (position < comment.getStart()) {
        return exact < 0 ? i - 1 : i;
      } else {
        return exact < 0 ? i : i + 1;
      }
    }
    return index;
  }

  /**
   * Returns the extended start position of the given node. Unlike
   * {@link ASTNode#getStart()} and {@link ASTNode#getLength()}, the extended
   * source range may include comments and whitespace immediately before or
   * after the normal source range for the node.
   *
   * @param node
   *            the node
   * @return the 0-based character index, or <code>-1</code> if no source
   *         position information is recorded for this node
   * @see #getExtendedLength(ASTNode)
   * @since 3.0
   */
  public int getExtendedStartPosition(ASTNode node) {
    if (this.leadingPtr >= 0) {
      long range = -1;
      for (int i = 0; range < 0 && i <= this.leadingPtr; i++) {
        if (this.leadingNodes[i] == node)
          range = this.leadingIndexes[i];
      }
      if (range >= 0) {
        return this.comments[(int) (range >> 32)].getStart();
      }
    }
    return node.getStart();
  }

  /*
   * Search the line number corresponding to a specific position between the
   * given line range (inclusive)
   *
   * @param position int
   *
   * @param lineRange size-2 int[]
   *
   * @return int
   */
  public final int getLineNumber(int position, int[] lineRange) {
    try {
      return this.document.getLineOfOffset(position);
    } catch (BadLocationException e) {
      throw new IllegalArgumentException(
          "getLineNumber() in DefaultCommentMapper with " + position); //$NON-NLS-1$
    }
  }

  /*
   * Returns the extended end position of the given node.
   */
  public int getExtendedEnd(ASTNode node) {
    int end = node.getStart() + node.getLength();
    if (this.trailingPtr >= 0) {
      long range = -1;
      for (int i = 0; range < 0 && i <= this.trailingPtr; i++) {
        if (this.trailingNodes[i] == node)
          range = this.trailingIndexes[i];
      }
      if (range >= 0) {
        Comment lastComment = this.comments[(int) range];
        end = lastComment.getStart() + lastComment.getLength();
      }
    }
    return end - 1;
  }

  /**
   * Returns the extended source length of the given node. Unlike
   * {@link ASTNode#getStart()} and {@link ASTNode#getLength()}, the extended
   * source range may include comments and whitespace immediately before or
   * after the normal source range for the node.
   *
   * @param node
   *            the node
   * @return a (possibly 0) length, or <code>0</code> if no source position
   *         information is recorded for this node
   * @see #getExtendedStartPosition(ASTNode)
   * @see #getExtendedEnd(ASTNode)
   * @since 3.0
   */
  public int getExtendedLength(ASTNode node) {
    return getExtendedEnd(node) - getExtendedStartPosition(node) + 1;
  }

  /**
   * Return index of first leading comment of a given node.
   *
   * @param node
   * @return index of first leading comment or -1 if node has no leading
   *         comment
   */
  int firstLeadingCommentIndex(ASTNode node) {
    if (this.leadingPtr >= 0) {
      for (int i = 0; i <= this.leadingPtr; i++) {
        if (this.leadingNodes[i] == node) {
          return (int) (this.leadingIndexes[i] >> 32);
        }
      }
    }
    return -1;
  }

  /**
   * Return index of last trailing comment of a given node.
   *
   * @param node
   * @return index of last trailing comment or -1 if node has no trailing
   *         comment
   */
  int lastTrailingCommentIndex(ASTNode node) {
    if (this.trailingPtr >= 0) {
      for (int i = 0; i <= this.trailingPtr; i++) {
        if (this.trailingNodes[i] == node) {
          return (int) this.trailingIndexes[i];
        }
      }
    }
    return -1;
  }

  /*
   * Initialize leading and trailing comments tables in whole nodes hierarchy
   * of a compilation unit. Scanner is necessary to scan between nodes and
   * comments and verify if there's nothing else than white spaces.
   */
  void initialize(Program unit, AstLexer sc, IDocument document) {

    if (document == null) {
      throw new IllegalArgumentException();
    }
    this.document = document;

    // Init array pointers
    this.leadingPtr = -1;
    this.trailingPtr = -1;

    // Init comments
    final Collection commentsCollection = unit.getComments();
    if (this.comments == null) {
      return;
    }
    this.comments = (Comment[]) commentsCollection
        .toArray(new Comment[commentsCollection.size()]);
    int size = this.comments.length;
    if (size == 0) {
      return;
    }

    // Init scanner and start ranges computing
    this.scanner = sc;
    // TODO : this.scanner.tokenizeWhiteSpace = true;

    // Start unit visit
    AbstractVisitor commentVisitor = new CommentMapperVisitor();
    unit.accept(commentVisitor);

    // Reduce leading arrays if necessary
    int leadingCount = this.leadingPtr + 1;
    if (leadingCount > 0 && leadingCount < this.leadingIndexes.length) {
      System.arraycopy(this.leadingNodes, 0,
          this.leadingNodes = new ASTNode[leadingCount], 0,
          leadingCount);
      System.arraycopy(this.leadingIndexes, 0,
          this.leadingIndexes = new long[leadingCount], 0,
          leadingCount);
    }

    // Reduce trailing arrays if necessary
    if (this.trailingPtr >= 0) {
      // remove last remaining unresolved nodes
      while (this.trailingIndexes[this.trailingPtr] == -1) {
        this.trailingPtr--;
        if (this.trailingPtr < 0) {
          this.trailingIndexes = null;
          this.trailingNodes = null;
          break;
        }
      }

      // reduce array size
      int trailingCount = this.trailingPtr + 1;
      if (trailingCount > 0
          && trailingCount < this.trailingIndexes.length) {
        System.arraycopy(this.trailingNodes, 0,
            this.trailingNodes = new ASTNode[trailingCount], 0,
            trailingCount);
        System.arraycopy(this.trailingIndexes, 0,
            this.trailingIndexes = new long[trailingCount], 0,
            trailingCount);
      }
    }

    // Release scanner as it's only used during unit visit
    this.scanner = null;
  }

  /**
   * Search and store node leading comments. Comments are searched in position
   * range from previous extended position to node start position. If one or
   * several comment are found, returns first comment start position,
   * otherwise returns node start position.
   * <p>
   * Starts to search for first comment before node start position and return
   * if none was found...
   * </p>
   * <p>
   * When first comment is found before node, goes up in comment list until
   * one of following conditions becomes true:
   * <ol>
   * <li>comment end is before previous end</li>
   * <li>comment start and previous end is on the same line but not on same
   * line of node start</li>
   * <li>there's other than white characters between current node and comment</li>
   * <li>TODO : there's more than 1 line between current node and comment</li>
   * </ol>
   * If some comment have been found, then no token should be on on the same
   * line before, so remove all comments which do not verify this assumption.
   * </p>
   * <p>
   * If finally there's leading still comments, then stores indexes of the
   * first and last one in leading comments table.
   */
  int storeLeadingComments(ASTNode node, int previousEnd,
      int[] parentLineRange) {
    // Init extended position
    int nodeStart = node.getStart();
    int extended = nodeStart;

    // Get line of node start position
    int previousEndLine = getLineNumber(previousEnd, parentLineRange);
    int nodeStartLine = getLineNumber(nodeStart, parentLineRange);

    // Find first comment index
    int idx = getCommentIndex(0, nodeStart, -1);
    if (idx == -1) {
      return nodeStart;
    }

    // Look after potential comments
    int startIdx = -1;
    int endIdx = idx;
    int previousStart = nodeStart;
    while (idx >= 0 && previousStart >= previousEnd) {
      // Verify for each comment that there's only white spaces between
      // end and start of {following comment|node}
      Comment comment = this.comments[idx];
      int commentStart = comment.getStart();
      int end = commentStart + comment.getLength() - 1;
      int commentLine = getLineNumber(commentStart, parentLineRange);
      if (end <= previousEnd
          || (commentLine == previousEndLine && commentLine != nodeStartLine)) {
        // stop search on condition 1) and 2)
        break;
      } else if ((end + 1) < previousStart) { // may be equals => then no
                          // scan is necessary
        try {
          resetTo(end + 1, previousStart);
          this.scanner.next_token();
          String token = this.scanner.yytext();
          if (token != null && token.trim().length() > 0) {
            // stop search on condition 3)
            // if first comment fails, then there's no extended
            // position in fact
            if (idx == endIdx) {
              return nodeStart;
            }
            break;
          }
        } catch (Exception e) {
          // Should not happen, but return no extended position...
          assert false;
          return nodeStart;
        }
      }
      // Store previous infos
      previousStart = commentStart;
      startIdx = idx--;
    }
    if (startIdx != -1) {
      // Store leading comments indexes
      if (startIdx <= endIdx) {
        if (++this.leadingPtr == 0) {
          this.leadingNodes = new ASTNode[STORAGE_INCREMENT];
          this.leadingIndexes = new long[STORAGE_INCREMENT];
        } else if (this.leadingPtr == this.leadingNodes.length) {
          int newLength = (this.leadingPtr * 3 / 2)
              + STORAGE_INCREMENT;
          System.arraycopy(this.leadingNodes, 0,
              this.leadingNodes = new ASTNode[newLength], 0,
              this.leadingPtr);
          System.arraycopy(this.leadingIndexes, 0,
              this.leadingIndexes = new long[newLength], 0,
              this.leadingPtr);
        }
        this.leadingNodes[this.leadingPtr] = node;
        this.leadingIndexes[this.leadingPtr] = (((long) startIdx) << 32)
            + endIdx;
        extended = this.comments[endIdx].getStart();
      }
    }
    return extended;
  }

  private void resetTo(int begin, int end) throws IOException {
    if (scanner == null) {
      throw new IllegalArgumentException(
          "null at resetTo(int begin, int end)"); //$NON-NLS-1$
    }
    this.scanner.yyreset(new IntervalDocumentReader(this.document, begin,
        end));
    this.scanner.setInScriptingState();
  }

  /**
   * Search and store node trailing comments. Comments are searched in
   * position range from node end position to specified next start. If one or
   * several comment are found, returns last comment end position, otherwise
   * returns node end position.
   * <p>
   * Starts to search for first comment after node end position and return if
   * none was found...
   * </p>
   * <p>
   * When first comment is found after node, goes down in comment list until
   * one of following conditions becomes true:
   * <ol>
   * <li>comment start is after next start</li>
   * <li>there's other than white characters between current node and comment</li>
   * <li>TODO there's more than 1 line between current node and comment</li>
   * </ol>
   * If at least potential comments have been found, then all of them has to
   * be separated from following node. So, remove all comments which do not
   * verify this assumption. Note that this verification is not applicable on
   * last node.
   * </p>
   * <p>
   * If finally there's still trailing comments, then stores indexes of the
   * first and last one in trailing comments table.
   */
  int storeTrailingComments(ASTNode node, int nextStart, boolean lastChild,
      int[] parentLineRange) {

    // Init extended position
    int nodeEnd = node.getStart() + node.getLength() - 1;
    if (nodeEnd == nextStart) {
      // special case for last child of its parent
      if (++this.trailingPtr == 0) {
        this.trailingNodes = new ASTNode[STORAGE_INCREMENT];
        this.trailingIndexes = new long[STORAGE_INCREMENT];
        this.lastTrailingPtr = -1;
      } else if (this.trailingPtr == this.trailingNodes.length) {
        int newLength = (this.trailingPtr * 3 / 2) + STORAGE_INCREMENT;
        System.arraycopy(this.trailingNodes, 0,
            this.trailingNodes = new ASTNode[newLength], 0,
            this.trailingPtr);
        System.arraycopy(this.trailingIndexes, 0,
            this.trailingIndexes = new long[newLength], 0,
            this.trailingPtr);
      }
      this.trailingNodes[this.trailingPtr] = node;
      this.trailingIndexes[this.trailingPtr] = -1;
      return nodeEnd;
    }
    int extended = nodeEnd;

    // Get line number
    int nodeEndLine = getLineNumber(nodeEnd, parentLineRange);

    // Find comments range index
    int idx = getCommentIndex(0, nodeEnd, 1);
    if (idx == -1) {
      return nodeEnd;
    }

    // Look after potential comments
    int startIdx = idx;
    int endIdx = -1;
    int length = this.comments.length;
    int commentStart = extended + 1;
    int previousEnd = nodeEnd + 1;
    int sameLineIdx = -1;
    while (idx < length && commentStart < nextStart) {
      // get comment and leave if next starting position has been reached
      Comment comment = this.comments[idx];
      commentStart = comment.getStart();
      // verify that there's nothing else than white spaces between
      // node/comments
      if (commentStart >= nextStart) {
        // stop search on condition 1)
        break;
      } else if (previousEnd < commentStart) {
        try {
          resetTo(previousEnd, commentStart);
          this.scanner.next_token();
          String token = this.scanner.yytext();
          if (token != null && token.trim().length() > 0) {
            // stop search on condition 2)
            // if first index fails, then there's no extended
            // position in fact...
            if (idx == startIdx) {
              return nodeEnd;
            }
            // otherwise we get the last index of trailing comment
            // => break
            break;
          }
        } catch (Exception e) {
          // Should not happen, but return no extended position...
          assert false;
          return nodeEnd;
        }
      }
      // Store index if we're on the same line than node end
      int commentLine = getLineNumber(commentStart, parentLineRange);
      if (commentLine == nodeEndLine) {
        sameLineIdx = idx;
      }
      // Store previous infos
      previousEnd = commentStart + comment.getLength();
      endIdx = idx++;
    }
    if (endIdx != -1) {
      // Verify that following node start is separated
      if (!lastChild) {
        int nextLine = getLineNumber(nextStart, parentLineRange);
        int previousLine = getLineNumber(previousEnd, parentLineRange);
        if ((nextLine - previousLine) <= 1) {
          if (sameLineIdx == -1)
            return nodeEnd;
          endIdx = sameLineIdx;
        }
      }
      // Store trailing comments indexes
      if (++this.trailingPtr == 0) {
        this.trailingNodes = new ASTNode[STORAGE_INCREMENT];
        this.trailingIndexes = new long[STORAGE_INCREMENT];
        this.lastTrailingPtr = -1;
      } else if (this.trailingPtr == this.trailingNodes.length) {
        int newLength = (this.trailingPtr * 3 / 2) + STORAGE_INCREMENT;
        System.arraycopy(this.trailingNodes, 0,
            this.trailingNodes = new ASTNode[newLength], 0,
            this.trailingPtr);
        System.arraycopy(this.trailingIndexes, 0,
            this.trailingIndexes = new long[newLength], 0,
            this.trailingPtr);
      }
      this.trailingNodes[this.trailingPtr] = node;
      long nodeRange = (((long) startIdx) << 32) + endIdx;
      this.trailingIndexes[this.trailingPtr] = nodeRange;
      // Compute new extended end
      extended = this.comments[endIdx].getStart()
          + this.comments[endIdx].getLength() - 1;
      // Look for children unresolved extended end
      ASTNode previousNode = node;
      int ptr = this.trailingPtr - 1; // children extended end were stored
                      // before
      while (ptr >= 0) {
        long range = this.trailingIndexes[ptr];
        if (range != -1)
          break; // there's no more unresolved nodes
        ASTNode unresolved = this.trailingNodes[ptr];
        if (previousNode != unresolved.getParent())
          break; // we're no longer in node ancestor hierarchy
        this.trailingIndexes[ptr] = nodeRange;
        previousNode = unresolved;
        ptr--; // get previous node
      }
      // Remove remaining unresolved nodes
      if (ptr > this.lastTrailingPtr) {
        int offset = ptr - this.lastTrailingPtr;
        for (int i = ptr + 1; i <= this.trailingPtr; i++) {
          this.trailingNodes[i - offset] = this.trailingNodes[i];
          this.trailingIndexes[i - offset] = this.trailingIndexes[i];
        }
        this.trailingPtr -= offset;
      }
      this.lastTrailingPtr = this.trailingPtr;
    }
    return extended;
  }

  class CommentMapperVisitor extends ApplyAll {

    ASTNode topSiblingParent = null;
    ASTNode[] siblings = new ASTNode[10];
    int[][] parentLineRange = new int[10][];
    int siblingPtr = -1;

    protected boolean apply(ASTNode node) {

      // Get default previous end
      ASTNode parent = node.getParent();
      int previousEnd = parent.getStart();

      // Look for sibling node
      ASTNode sibling = parent == this.topSiblingParent ? (ASTNode) this.siblings[this.siblingPtr]
          : null;
      if (sibling != null) {
        // Found one previous sibling, so compute its trailing comments
        // using current node start position
        try {
          previousEnd = storeTrailingComments(sibling,
              node.getStart(), false,
              this.parentLineRange[this.siblingPtr]);
        } catch (Exception ex) {
          // Give up extended ranges at this level if unexpected
          // exception happens...
        }
      }

      // Stop visit for malformed node (see bug
      // https://bugs.eclipse.org/bugs/show_bug.cgi?id=84049)
      if (node.getType() == ASTNode.AST_ERROR) {
        return false;
      }

      // Compute leading comments for current node
      int[] previousLineRange = this.siblingPtr > -1 ? this.parentLineRange[this.siblingPtr]
          : new int[] {
              1,
              DefaultCommentMapper.this.document
                  .getNumberOfLines() };
      try {
        storeLeadingComments(node, previousEnd, previousLineRange);
      } catch (Exception ex) {
        // Give up extended ranges at this level if unexpected exception
        // happens...
      }

      // Store current node as waiting sibling for its parent
      if (this.topSiblingParent != parent) {
        if (this.siblings.length == ++this.siblingPtr) {
          System.arraycopy(this.siblings, 0,
              this.siblings = new ASTNode[this.siblingPtr * 2],
              0, this.siblingPtr);
          System.arraycopy(
              this.parentLineRange,
              0,
              this.parentLineRange = new int[this.siblingPtr * 2][],
              0, this.siblingPtr);
        }
        if (this.topSiblingParent == null) {
          // node is a CompilationUnit
          this.parentLineRange[this.siblingPtr] = previousLineRange;
        } else {
          int parentStart = parent.getStart();
          int firstLine = getLineNumber(parentStart,
              previousLineRange);
          int lastLine = getLineNumber(
              parentStart + parent.getLength() - 1,
              previousLineRange);
          if (this.parentLineRange[this.siblingPtr] == null) {
            this.parentLineRange[this.siblingPtr] = new int[] {
                firstLine, lastLine };
          } else {
            int[] lineRange = this.parentLineRange[this.siblingPtr];
            lineRange[0] = firstLine;
            lineRange[1] = lastLine;
          }
        }
        this.topSiblingParent = parent;
      }
      this.siblings[this.siblingPtr] = node;

      // We're always ok to visit sub-levels
      return true;
    }

    public void endVisitNode(ASTNode node) {

      // Look if a child node is waiting for trailing comments computing
      ASTNode sibling = this.topSiblingParent == node ? (ASTNode) this.siblings[this.siblingPtr]
          : null;
      if (sibling != null) {
        try {
          storeTrailingComments(sibling,
              node.getStart() + node.getLength() - 1, true,
              this.parentLineRange[this.siblingPtr]);
        } catch (Exception ex) {
          // Give up extended ranges at this level if unexpected
          // exception happens...
        }
      }
      // Remove sibling if needed
      if (this.topSiblingParent != null /* not a CompilationUnit */
          && this.topSiblingParent == node) {
        this.siblingPtr--;
        this.topSiblingParent = node.getParent();
      }
    }

    public boolean visit(Program node) {
      return true;
    }

    public boolean visit(Comment node) {
      // don't visit comments
      return false;
    }
  }

  /**
   * Returns a stream that represents the document
   *
   * @param StructuredDocument
   * @param start
   * @param length
   */
  public static class IntervalDocumentReader extends Reader {

    private static final String BAD_LOCATION_ERROR = "Bad location error "; //$NON-NLS-1$

    final private IDocument parent;
    private int startPhpRegion;
    final private int endPhpRegion;

    public IntervalDocumentReader(final IDocument parent,
        final int startPhpRegion, final int endPhpRegion) {
      this.parent = parent;
      this.startPhpRegion = startPhpRegion;
      this.endPhpRegion = endPhpRegion;
    }

    @Override
    public int read() throws IOException {
      try {
        return startPhpRegion < endPhpRegion ? parent
            .getChar(startPhpRegion++) : -1;
      } catch (BadLocationException e) {
        throw new IOException(BAD_LOCATION_ERROR + startPhpRegion);
      }
    }

    @Override
    public int read(char[] b, int off, int len) throws IOException {
      if (b == null) {
        throw new NullPointerException();
      } else if ((off < 0) || (off > b.length) || (len < 0)
          || ((off + len) > b.length) || ((off + len) < 0)) {
        throw new IndexOutOfBoundsException();
      } else if (len == 0) {
        return 0;
      }

      int c = read();
      if (c == -1) {
        return -1;
      }
      b[off] = (char) c;

      int i = 1;
      for (; i < len; i++) {
        c = read();
        if (c == -1) {
          break;
        }
        b[off + i] = (char) c;
      }
      return i;
    }

    @Override
    public void close() throws IOException {
    }
  }

}
TOP

Related Classes of org.eclipse.php.internal.core.ast.nodes.DefaultCommentMapper$CommentMapperVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.