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

Source Code of org.eclipse.php.internal.core.ast.nodes.Program

/*******************************************************************************
* 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.util.*;

import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ISourceReference;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.php.internal.core.PHPCorePlugin;
import org.eclipse.php.internal.core.PHPVersion;
import org.eclipse.php.internal.core.ast.locator.Locator;
import org.eclipse.php.internal.core.ast.match.ASTMatcher;
import org.eclipse.php.internal.core.ast.scanner.AstLexer;
import org.eclipse.php.internal.core.ast.visitor.Visitor;
import org.eclipse.text.edits.TextEdit;

/**
* The AST root node for PHP program (meaning a PHP file). The program holds
* array of statements such as Class, Function and evaluation statement. The
* program also holds the PHP file comments.
*
* @author Moshe S. & Roy G. 2007
*/
public class Program extends ASTNode {

  /**
   * Statements array of php program
   */
  private final ASTNode.NodeList<Statement> statements = new ASTNode.NodeList<Statement>(
      STATEMENTS_PROPERTY);

  /**
   * Comments array of the php program
   */
  private final ASTNode.NodeList<Comment> comments = new ASTNode.NodeList<Comment>(
      COMMENTS_PROPERTY);

  /**
   * The structural property of this node type.
   */
  public static final ChildListPropertyDescriptor STATEMENTS_PROPERTY = new ChildListPropertyDescriptor(
      Program.class, "statements", Statement.class, NO_CYCLE_RISK); //$NON-NLS-1$
  public static final ChildListPropertyDescriptor COMMENTS_PROPERTY = new ChildListPropertyDescriptor(
      Program.class, "comments", Comment.class, NO_CYCLE_RISK); //$NON-NLS-1$

  /**
   * A list of property descriptors (element type:
   * {@link StructuralPropertyDescriptor}), or null if uninitialized.
   */
  private static final List<StructuralPropertyDescriptor> PROPERTY_DESCRIPTORS;
  static {
    List<StructuralPropertyDescriptor> properyList = new ArrayList<StructuralPropertyDescriptor>(
        1);
    properyList.add(STATEMENTS_PROPERTY);
    properyList.add(COMMENTS_PROPERTY);
    PROPERTY_DESCRIPTORS = Collections.unmodifiableList(properyList);
  }

  /**
   * The comment mapper, or <code>null</code> if none; initially
   * <code>null</code>.
   *
   * @since 3.0
   */
  private DefaultCommentMapper commentMapper = null;

  /**
   * The php type root (an <code>org.eclipse.dltk.ISourceModule</code>) this
   * source module was created from, or <code>null</code> if it was not
   * created from a php type root.
   */
  private ISourceModule sourceModule = null;

  /**
   * Line end table. If <code>lineEndTable[i] == p</code> then the line number
   * <code>i+1</code> ends at character position <code>p</code>. Except for
   * the last line, the positions are that of the last character of the line
   * delimiter. For example, the source string <code>A\nB\nC</code> has line
   * end table {1, 3} (if \n is one character).
   */
  private int[] lineEndTable = {};

  private Program(int start, int end, AST ast, Statement[] statements,
      List comments) {
    super(start, end, ast);

    if (statements == null || comments == null) {
      throw new IllegalArgumentException();
    }

    for (Statement statement : statements) {
      this.statements.add(statement);
    }
    for (Object comment : comments) {
      this.comments.add((Comment) comment);
    }
  }

  public Program(int start, int end, AST ast, List statements,
      List commentList) {
    this(start, end, ast, (Statement[]) statements
        .toArray(new Statement[statements.size()]), commentList);
  }

  public Program(AST ast) {
    super(ast);
  }

  /**
   * Returns a list of the comments encountered while parsing this source
   * program.
   * <p>
   * Since the PHP language allows comments to appear most anywhere in the
   * source text, it is problematic to locate comments in relation to the
   * structure of an AST. The one exception is doc comments which, by
   * convention, immediately precede type, field, and method declarations;
   * these comments are located in the AST by
   * {@link BodyDeclaration#getPhpdoc BodyDeclaration.getPhpdoc}. Other
   * comments do not show up in the AST. The table of comments is provided for
   * clients that need to find the source ranges of all comments in the
   * original source string. It includes entries for comments of all kinds
   * (line, block, and doc), arranged in order of increasing source position.
   * </p>
   * <p>
   * Note on comment parenting: The {@link ASTNode#getParent() getParent()} of
   * a doc comment associated with a body declaration is the body declaration
   * node; for these comment nodes {@link ASTNode#getRoot() getRoot()} will
   * return the program (assuming an unmodified AST) reflecting the fact that
   * these nodes are property located in the AST for the compilation unit.
   * However, for other comment nodes, {@link ASTNode#getParent() getParent()}
   * will return <code>null</code>, and {@link ASTNode#getRoot() getRoot()}
   * will return the comment node itself, indicating that these comment nodes
   * are not directly connected to the AST for the compilation unit. The
   * {@link Comment#getAlternateRoot Comment.getAlternateRoot} method provides
   * a way to navigate from a comment to its source program
   * </p>
   * <p>
   * Clients cannot modify the resulting list.
   * </p>
   *
   * @return an unmodifiable list of comments in increasing order of source
   *         start position, or <code>null</code> if comment information for
   *         this compilation unit is not available
   * @see ASTParser
   * @since 3.0
   */
  public List<Comment> comments() {
    return comments;
  }

  /**
   * @deprecated use {@link #comments()}
   */
  public Collection<Comment> getComments() {
    return Collections.unmodifiableCollection(comments);
  }

  /**
   * see {@link #sourceModule}
   *
   * @param typeRoot
   */
  public void setSourceModule(ISourceModule typeRoot) {
    this.sourceModule = typeRoot;
  }

  /**
   * see {@link #sourceModule} return {@link #sourceModule}
   */
  public ISourceModule getSourceModule() {
    return sourceModule;
  }

  public void accept0(Visitor visitor) {
    final boolean visit = visitor.visit(this);
    if (visit) {
      childrenAccept(visitor);
    }
    visitor.endVisit(this);
  }

  public void childrenAccept(Visitor visitor) {
    for (ASTNode node : this.statements) {
      node.accept(visitor);
    }
    for (ASTNode node : this.comments) {
      node.accept(visitor);
    }
  }

  public void traverseTopDown(Visitor visitor) {
    accept(visitor);
    for (ASTNode node : this.statements) {
      node.traverseTopDown(visitor);
    }
    for (ASTNode node : this.comments) {
      node.traverseTopDown(visitor);
    }
  }

  public void traverseBottomUp(Visitor visitor) {
    for (ASTNode node : this.statements) {
      node.traverseBottomUp(visitor);
    }
    for (ASTNode node : this.comments) {
      node.traverseBottomUp(visitor);
    }
    accept(visitor);
  }

  /**
   * create program node in XML format.
   */
  public void toString(StringBuffer buffer, String tab) {
    buffer.append("<Program"); //$NON-NLS-1$
    appendInterval(buffer);
    buffer.append(">\n").append(TAB).append("<Statements>\n"); //$NON-NLS-1$ //$NON-NLS-2$
    for (ASTNode node : this.statements) {
      node.toString(buffer, TAB + TAB + tab);
      buffer.append("\n"); //$NON-NLS-1$
    }
    buffer.append(TAB)
        .append("</Statements>\n").append(TAB).append("<Comments>\n"); //$NON-NLS-1$ //$NON-NLS-2$
    for (ASTNode comment : this.comments) {
      comment.toString(buffer, TAB + TAB + tab);
      buffer.append("\n"); //$NON-NLS-1$
    }
    buffer.append(TAB).append("</Comments>\n").append("</Program>"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  public int getType() {
    return ASTNode.PROGRAM;
  }

  /**
   * @deprecated use {@link #statements()}
   */
  public Statement[] getStatements() {
    return statements.toArray(new Statement[this.statements.size()]);
  }

  /**
   * Retrieves the statement list of this program
   *
   * @return statement parts of this program
   */
  public List<Statement> statements() {
    return this.statements;
  }

  public ASTNode getElementAt(int offset) {
    return Locator.locateNode(this, offset);
  }

  /*
   * Method declared on ASTNode.
   */
  public boolean subtreeMatch(ASTMatcher matcher, Object other) {
    // dispatch to correct overloaded match method
    return matcher.match(this, other);
  }

  /**
   * Initializes the internal comment mapper with the given scanner.
   *
   * @param scanner
   *            the scanner
   * @since 3.0
   */
  public void initCommentMapper(IDocument document, AstLexer scanner) {
    this.commentMapper = new DefaultCommentMapper(this.getComments()
        .toArray(new Comment[this.getComments().size()]));
    this.commentMapper.initialize(this, scanner, document);
  }

  /**
   * Returns the internal comment mapper.
   *
   * @return the comment mapper, or <code>null</code> if none.
   * @since 3.0
   */
  DefaultCommentMapper getCommentMapper() {
    return this.commentMapper;
  }

  /**
   * Sets the line end table for this compilation unit. If
   * <code>lineEndTable[i] == p</code> then line number <code>i+1</code> ends
   * at character position <code>p</code>. Except for the last line, the
   * positions are that of (the last character of) the line delimiter. For
   * example, the source string <code>A\nB\nC</code> has line end table {1, 3,
   * 4}.
   *
   * @param lineEndTable
   *            the line end table
   */
  public void setLineEndTable(int[] lineEndTable) {
    if (lineEndTable == null) {
      throw new NullPointerException();
    }
    // alternate root is *not* considered a structural property
    // but we protect them nevertheless
    // Disable the check for bug
    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=291569
    // checkModifiable();
    this.lineEndTable = lineEndTable;
  }

  /**
   * Returns the column number corresponding to the given source character
   * position in the original source string. Column number are zero-based.
   * Return <code>-1</code> if it is beyond the valid range or <code>-2</code>
   * if the column number information is unknown.
   *
   * @param position
   *            a 0-based character position, possibly negative or out of
   *            range
   * @return the 0-based column number, or <code>-1</code> if the character
   *         position does not correspond to a source line in the original
   *         source file or <code>-2</code> if column number information is
   *         unknown for this compilation unit
   * @see ASTParser
   * @since 3.2
   */
  public int getColumnNumber(final int position) {
    if (this.lineEndTable == null)
      return -2;
    final int line = getLineNumber(position);
    if (line == -1) {
      return -1;
    }
    if (line == 1) {
      if (position >= getStart() + getLength())
        return -1;
      return position;
    }
    // length is different from 0
    int length = this.lineEndTable.length;
    // -1 to for one-based to zero-based conversion.
    // -1, again, to get previous line.
    final int previousLineOffset = this.lineEndTable[line - 2];
    // previousLineOffset + 1 is the first character of the current line
    final int offsetForLine = previousLineOffset + 1;
    final int currentLineEnd = line == length + 1 ? getStart()
        + getLength() - 1 : this.lineEndTable[line - 1];
    if (offsetForLine > currentLineEnd) {
      return -1;
    } else {
      return position - offsetForLine;
    }
  }

  /**
   * Returns the line number corresponding to the given source character
   * position in the original source string. The initial line of the
   * compilation unit is numbered 1, and each line extends through the last
   * character of the end-of-line delimiter. The very last line extends
   * through the end of the source string and has no line delimiter. For
   * example, the source string <code>class A\n{\n}</code> has 3 lines
   * corresponding to inclusive character ranges [0,7], [8,9], and [10,10].
   * Returns -1 for a character position that does not correspond to any
   * source line, or -2 if no line number information is available for this
   * compilation unit.
   *
   * @param position
   *            a 0-based character position, possibly negative or out of
   *            range
   * @return the 1-based line number, or <code>-1</code> if the character
   *         position does not correspond to a source line in the original
   *         source file or <code>-2</code> if line number information is not
   *         known for this compilation unit
   * @see ASTParser
   * @since 3.2
   */
  public int getLineNumber(int position) {
    if (this.lineEndTable == null)
      return -2;
    int length;
    if ((length = this.lineEndTable.length) == 0) {
      if (position >= getStart() + getLength()) {
        return -1;
      }
      return 1;
    }
    int low = 0;
    if (position < 0) {
      // position illegal
      return -1;
    }
    if (position <= this.lineEndTable[low]) {
      // before the first line delimiter
      return 1;
    }
    // assert position > lineEndTable[low+1] && low == 0
    int hi = length - 1;
    if (position > this.lineEndTable[hi]) {
      // position beyond the last line separator
      if (position >= getStart() + getLength()) {
        // this is beyond the end of the source length
        return -1;
      } else {
        return length + 1;
      }
    }
    // assert lineEndTable[low] < position <= lineEndTable[hi]
    // && low == 0 && hi == length - 1 && low < hi

    // binary search line end table
    while (true) {
      // invariant lineEndTable[low] < position <= lineEndTable[hi]
      // && 0 <= low < hi <= length - 1
      // reducing measure hi - low
      if (low + 1 == hi) {
        // assert lineEndTable[low] < position <= lineEndTable[low+1]
        // position is on line low+1 (line number is low+2)
        return low + 2;
      }
      // assert hi - low >= 2, so average is truly in between
      int mid = low + (hi - low) / 2;
      // assert 0 <= low < mid < hi <= length - 1
      if (position <= this.lineEndTable[mid]) {
        // assert lineEndTable[low] < position <= lineEndTable[mid]
        // && 0 <= low < mid < hi <= length - 1
        hi = mid;
      } else {
        // position > lineEndTable[mid]
        // assert lineEndTable[mid] < position <= lineEndTable[hi]
        // && 0 <= low < mid < hi <= length - 1
        low = mid;
      }
      // in both cases, invariant reachieved with reduced measure
    }
  }

  /**
   * Given a line number and column number, returns the corresponding position
   * in the original source string. Returns -2 if no line number information
   * is available for this compilation unit. Returns the total size of the
   * source string if <code>line</code> is greater than the actual number
   * lines in the unit. Returns -1 if <code>column</code> is less than 0, or
   * the position of the last character of the line if <code>column</code> is
   * beyond the legal range, or the given line number is less than one.
   *
   * @param line
   *            the one-based line number
   * @param column
   *            the zero-based column number
   * @return the 0-based character position in the source string;
   *         <code>-2</code> if line/column number information is not known
   *         for this compilation unit or <code>-1</code> the inputs are not
   *         valid
   * @since 3.2
   */
  public int getPosition(int line, int column) {
    if (this.lineEndTable == null)
      return -2;
    if (line < 1 || column < 0)
      return -1;
    int length;
    if ((length = this.lineEndTable.length) == 0) {
      if (line != 1)
        return -1;
      return column >= getStart() + getLength() ? -1 : column;
    }
    if (line == 1) {
      final int endOfLine = this.lineEndTable[0];
      return column > endOfLine ? -1 : column;
    } else if (line > length + 1) {
      // greater than the number of lines in the source string.
      return -1;
    }
    // -1 to for one-based to zero-based conversion.
    // -1, again, to get previous line.
    final int previousLineOffset = this.lineEndTable[line - 2];
    // previousLineOffset + 1 is the first character of the current line
    final int offsetForLine = previousLineOffset + 1;
    final int currentLineEnd = line == length + 1 ? getStart()
        + getLength() - 1 : this.lineEndTable[line - 1];
    if ((offsetForLine + column) > currentLineEnd) {
      return -1;
    } else {
      return offsetForLine + column;
    }
  }

  /**
   * Returns the extended source length of the given node. Unlike
   * {@link ASTNode#getStartPosition()} 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)
   * @since 3.0
   */
  public int getExtendedLength(ASTNode node) {
    if (node == null) {
      throw new IllegalArgumentException();
    }
    if (this.commentMapper == null || node.getAST() != getAST()) {
      // fall back: use best info available
      return node.getLength();
    } else {
      return this.commentMapper.getExtendedLength(node);
    }
  }

  /**
   * Returns the extended start position of the given node. Unlike
   * {@link ASTNode#getStartPosition()} 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 (node == null) {
      throw new IllegalArgumentException();
    }
    if (this.commentMapper == null || node.getAST() != getAST()) {
      // fall back: use best info available
      return node.getStart();
    } else {
      return this.commentMapper.getExtendedStartPosition(node);
    }
  }

  /**
   * Return the index in the whole comments list {@link #getComments() } of the
   * first leading comments associated with the given node.
   *
   * @param node
   *            the node
   * @return 0-based index of first leading comment or -1 if node has no
   *         associated comment before its start position.
   * @since 3.2
   */
  public int firstLeadingCommentIndex(ASTNode node) {
    if (node == null) {
      throw new IllegalArgumentException();
    }
    if (this.commentMapper == null || node.getAST() != getAST()) {
      return -1;
    }
    return this.commentMapper.firstLeadingCommentIndex(node);
  }

  /**
   * Return the index in the whole comments list {@link #getComments() } of the
   * last trailing comments associated with the given node.
   *
   * @param node
   *            the node
   * @return 0-based index of last trailing comment or -1 if node has no
   *         associated comment after its end position.
   * @since 3.2
   */
  public int lastTrailingCommentIndex(ASTNode node) {
    if (node == null) {
      throw new IllegalArgumentException();
    }
    if (this.commentMapper == null || node.getAST() != getAST()) {
      return -1;
    }
    return this.commentMapper.lastTrailingCommentIndex(node);
  }

  /**
   * Enables the recording of changes to this program unit and its
   * descendents. The program must have been created by <code>ASTParser</code>
   * and still be in its original state. Once recording is on, arbitrary
   * changes to the subtree rooted at this compilation unit are recorded
   * internally. Once the modification has been completed, call
   * <code>rewrite</code> to get an object representing the corresponding
   * edits to the original source code string.
   *
   * @exception IllegalArgumentException
   *                if this program is marked as unmodifiable, or if this
   *                program has already been tampered with, or recording has
   *                already been enabled
   * @since 3.0
   */
  public void recordModifications() {
    getAST().recordModifications(this);
  }

  /**
   * Converts all modifications recorded for this program into an object
   * representing the corresponding text edits to the given document
   * containing the original source code for this compilation unit.
   * <p>
   * The program must have been created by <code>ASTParser</code> from the
   * source code string in the given document, and recording must have been
   * turned on with a prior call to <code>recordModifications</code> while the
   * AST was still in its original state.
   * </p>
   * <p>
   * Calling this methods does not discard the modifications on record.
   * Subsequence modifications made to the AST are added to the ones already
   * on record. If this method is called again later, the resulting text edit
   * object will accurately reflect the net cumulative affect of all those
   * changes.
   * </p>
   *
   * @param document
   *            original document containing source code for this program
   * @param options
   *            the table of formatter options (key type: <code>String</code>;
   *            value type: <code>String</code>); or <code>null</code> to use
   *            the standard global options
   *            {@link org.eclipse.php.core.PhpPluginCore#getOptions()}.
   * @return text edit object describing the changes to the document
   *         corresponding to the recorded AST modifications
   * @exception IllegalArgumentException
   *                if the document passed is <code>null</code> or does not
   *                correspond to this AST
   * @exception IllegalStateException
   *                if <code>recordModifications</code> was not called to
   *                enable recording
   * @see #recordModifications()
   * @since 3.0
   */
  public TextEdit rewrite(IDocument document, Map options) {
    return getAST().rewrite(document, options);
  }

  ASTNode clone0(AST target) {
    final List statements = ASTNode.copySubtrees(target, statements());
    final List comments = ASTNode.copySubtrees(target, comments());
    final Program result = new Program(this.getStart(), this.getEnd(),
        target, statements, comments);
    return result;
  }

  @Override
  List<StructuralPropertyDescriptor> internalStructuralPropertiesForType(
      PHPVersion apiLevel) {
    return PROPERTY_DESCRIPTORS;
  }

  /*
   * (omit javadoc for this method) Method declared on ASTNode.
   */
  final List internalGetChildListProperty(ChildListPropertyDescriptor property) {
    if (property == STATEMENTS_PROPERTY) {
      return statements();
    }
    if (property == COMMENTS_PROPERTY) {
      return comments();
    }
    // allow default implementation to flag the error
    return super.internalGetChildListProperty(property);
  }

  /**
   * Finds an AST node by specified binding
   *
   * @param binding
   * @return
   */
  public ASTNode findDeclaringNode(IBinding binding) {
    ISourceReference phpElement = (ISourceReference) binding
        .getPHPElement();
    try {
      return getElementAt(phpElement.getSourceRange().getOffset());
    } catch (ModelException e) {
      PHPCorePlugin.log(e);
    }
    return null;
  }
}
TOP

Related Classes of org.eclipse.php.internal.core.ast.nodes.Program

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.