/******************************************************************************* * Copyright (c) 2000, 2005 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 *******************************************************************************/ package org.eclipse.jdt.core.dom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.parser.Scanner; import org.eclipse.jface.text.IDocument; import org.eclipse.text.edits.TextEdit; /** * Java compilation unit AST node type. This is the type of the root of an AST. *

* The source range for this type of node is ordinarily the entire source file, * including leading and trailing whitespace and comments. *

* For JLS2: *
 * CompilationUnit:
 *    [ PackageDeclaration ]
 *        { ImportDeclaration }
 *        { TypeDeclaration | ; }
 * 
* For JLS3, the kinds of type declarations * grew to include enum and annotation type declarations: *
 * CompilationUnit:
 *    [ PackageDeclaration ]
 *        { ImportDeclaration }
 *        { TypeDeclaration | EnumDeclaration | AnnotationTypeDeclaration | ; }
 * 
* * @since 2.0 */ public class CompilationUnit extends ASTNode { /** * The "package" structural property of this node type. * * @since 3.0 */ public static final ChildPropertyDescriptor PACKAGE_PROPERTY = new ChildPropertyDescriptor(CompilationUnit.class, "package", PackageDeclaration.class, OPTIONAL, NO_CYCLE_RISK); //$NON-NLS-1$ /** * The "imports" structural property of this node type. * * @since 3.0 */ public static final ChildListPropertyDescriptor IMPORTS_PROPERTY = new ChildListPropertyDescriptor(CompilationUnit.class, "imports", ImportDeclaration.class, NO_CYCLE_RISK); //$NON-NLS-1$ /** * The "types" structural property of this node type. * * @since 3.0 */ public static final ChildListPropertyDescriptor TYPES_PROPERTY = new ChildListPropertyDescriptor(CompilationUnit.class, "types", AbstractTypeDeclaration.class, CYCLE_RISK); //$NON-NLS-1$ /** * A list of property descriptors (element type: * {@link StructuralPropertyDescriptor}), * or null if uninitialized. * @since 3.0 */ private static final List PROPERTY_DESCRIPTORS; static { List properyList = new ArrayList(4); createPropertyList(CompilationUnit.class, properyList); addProperty(PACKAGE_PROPERTY, properyList); addProperty(IMPORTS_PROPERTY, properyList); addProperty(TYPES_PROPERTY, properyList); PROPERTY_DESCRIPTORS = reapPropertyList(properyList); } /** * Returns a list of structural property descriptors for this node type. * Clients must not modify the result. * * @param apiLevel the API level; one of the * AST.JLS* constants * @return a list of property descriptors (element type: * {@link StructuralPropertyDescriptor}) * @since 3.0 */ public static List propertyDescriptors(int apiLevel) { return PROPERTY_DESCRIPTORS; } /** * The comment table, or null if none; initially * null. This array is the storage underlying * the optionalCommentList ArrayList. * @since 3.0 */ Comment[] optionalCommentTable = null; /** * The comment list (element type: Comment, * or null if none; initially null. * @since 3.0 */ private List optionalCommentList = null; /** * The package declaration, or null if none; initially * null. */ private PackageDeclaration optionalPackageDeclaration = null; /** * The list of import declarations in textual order order; * initially none (elementType: ImportDeclaration). */ private ASTNode.NodeList imports = new ASTNode.NodeList(IMPORTS_PROPERTY); /** * The list of type declarations in textual order order; * initially none (elementType: AbstractTypeDeclaration) */ private ASTNode.NodeList types = new ASTNode.NodeList(TYPES_PROPERTY); /** * Line end table. If lineEndTable[i] == p then the * line number i+1 ends at character position * p. Except for the last line, the positions are that * of the last character of the line delimiter. * For example, the source string A\nB\nC has * line end table {1, 3} (if \n is one character). */ private int[] lineEndTable = new int[0]; /** * Canonical empty list of messages. */ private static final Message[] EMPTY_MESSAGES = new Message[0]; /** * Canonical empty list of problems. */ private static final IProblem[] EMPTY_PROBLEMS = new IProblem[0]; /** * Messages reported by the compiler during parsing or name resolution. */ private Message[] messages; /** * Problems reported by the compiler during parsing or name resolution. */ private IProblem[] problems = EMPTY_PROBLEMS; /** * The comment mapper, or null if none; * initially null. * @since 3.0 */ private DefaultCommentMapper commentMapper = null; /** * The Java element (an org.eclipse.jdt.core.ICompilationUnit or an org.eclipse.jdt.core.IClassFile) * this compilation unit was created from, or null if it was not created from a Java element. * @since 3.1 */ private IJavaElement element = null; /** * Sets the line end table for this compilation unit. * If lineEndTable[i] == p then line number i+1 * ends at character position p. Except for the last line, the * positions are that of (the last character of) the line delimiter. * For example, the source string A\nB\nC has * line end table {1, 3, 4}. * * @param lineEndTable the line end table */ void setLineEndTable(int[] lineEndTable) { if (lineEndTable == null) { throw new NullPointerException(); } // alternate root is *not* considered a structural property // but we protect them nevertheless checkModifiable(); this.lineEndTable = lineEndTable; } /** * Creates a new AST node for a compilation owned by the given AST. * The compilation unit initially has no package declaration, no * import declarations, and no type declarations. *

* N.B. This constructor is package-private; all subclasses must be * declared in the same package; clients are unable to declare * additional subclasses. *

* * @param ast the AST that is to own this node */ CompilationUnit(AST ast) { super(ast); } /* (omit javadoc for this method) * Method declared on ASTNode. * @since 3.0 */ final List internalStructuralPropertiesForType(int apiLevel) { return propertyDescriptors(apiLevel); } /* (omit javadoc for this method) * Method declared on ASTNode. */ final ASTNode internalGetSetChildProperty(ChildPropertyDescriptor property, boolean get, ASTNode child) { if (property == PACKAGE_PROPERTY) { if (get) { return getPackage(); } else { setPackage((PackageDeclaration) child); return null; } } // allow default implementation to flag the error return super.internalGetSetChildProperty(property, get, child); } /* (omit javadoc for this method) * Method declared on ASTNode. */ final List internalGetChildListProperty(ChildListPropertyDescriptor property) { if (property == IMPORTS_PROPERTY) { return imports(); } if (property == TYPES_PROPERTY) { return types(); } // allow default implementation to flag the error return super.internalGetChildListProperty(property); } /* (omit javadoc for this method) * Method declared on ASTNode. */ final int getNodeType0() { return COMPILATION_UNIT; } /* (omit javadoc for this method) * Method declared on ASTNode. */ ASTNode clone0(AST target) { CompilationUnit result = new CompilationUnit(target); // n.b do not copy line number table or messages result.setSourceRange(this.getStartPosition(), this.getLength()); result.setPackage( (PackageDeclaration) ASTNode.copySubtree(target, getPackage())); result.imports().addAll(ASTNode.copySubtrees(target, imports())); result.types().addAll(ASTNode.copySubtrees(target, types())); return result; } /* (omit javadoc for this method) * Method declared on ASTNode. */ final boolean subtreeMatch0(ASTMatcher matcher, Object other) { // dispatch to correct overloaded match method return matcher.match(this, other); } /* (omit javadoc for this method) * Method declared on ASTNode. */ void accept0(ASTVisitor visitor) { boolean visitChildren = visitor.visit(this); if (visitChildren) { // visit children in normal left to right reading order acceptChild(visitor, getPackage()); acceptChildren(visitor, this.imports); acceptChildren(visitor, this.types); } visitor.endVisit(this); } /** * Returns the node for the package declaration of this compilation * unit, or null if this compilation unit is in the * default package. * * @return the package declaration node, or null if none */ public PackageDeclaration getPackage() { return this.optionalPackageDeclaration; } /** * Sets or clears the package declaration of this compilation unit * node to the given package declaration node. * * @param pkgDecl the new package declaration node, or * null if this compilation unit does not have a package * declaration (that is in the default package) * @exception IllegalArgumentException if: * */ public void setPackage(PackageDeclaration pkgDecl) { ASTNode oldChild = this.optionalPackageDeclaration; preReplaceChild(oldChild, pkgDecl, PACKAGE_PROPERTY); this.optionalPackageDeclaration = pkgDecl; postReplaceChild(oldChild, pkgDecl, PACKAGE_PROPERTY); } /** * Returns the live list of nodes for the import declarations of this * compilation unit, in order of appearance. * * @return the live list of import declaration nodes * (elementType: ImportDeclaration) */ public List imports() { return this.imports; } /** * Returns the live list of nodes for the top-level type declarations of this * compilation unit, in order of appearance. *

* Note that in JLS3, the types may include both enum declarations * and annotation type declarations introduced in J2SE 5. * For JLS2, the elements are always TypeDeclaration. *

* * @return the live list of top-level type declaration * nodes (elementType: AbstractTypeDeclaration) */ public List types() { return this.types; } /** * Finds the corresponding AST node in the given compilation unit from * which the given binding originated. Returns null if the * binding does not correspond to any node in this compilation unit. * This method always returns null if bindings were not requested * when this AST was built. *

* The following table indicates the expected node type for the various * different kinds of bindings: *

*

*

* Each call to {@link ASTParser#createAST(org.eclipse.core.runtime.IProgressMonitor)} with a request for bindings * gives rise to separate universe of binding objects. This method always returns * null when the binding object comes from a different AST. * Use findDeclaringNode(binding.getKey()) when the binding comes * from a different AST. *

* * @param binding the binding * @return the corresponding node where the given binding is declared, * or null if the binding does not correspond to a node in this * compilation unit or if bindings were not requested when this AST was built * @see #findDeclaringNode(String) */ public ASTNode findDeclaringNode(IBinding binding) { return this.ast.getBindingResolver().findDeclaringNode(binding); } /** * Finds the corresponding AST node in the given compilation unit from * which the given resolved annotation originated. Returns null * if the resolved annotation does not correspond to any node in this compilation unit. * * This method always returns null when the resolved annotation * comes from a different AST. * * @param resolvedAnnotation the resolved annotation * @return the corresponding node where the given resolved annotation is declared, * or null if the resolved annotation does not correspond to a node in this * compilation unit or if bindings were not requested when this AST was built * @since 3.2 */ public ASTNode findDeclaringNode(IResolvedAnnotation resolvedAnnotation) { return this.ast.getBindingResolver().findDeclaringNode(resolvedAnnotation); } /** * Finds the corresponding AST node in the given compilation unit from * which the binding with the given key originated. Returns * null if the corresponding node cannot be determined. * This method always returns null if bindings were not requested * when this AST was built. *

* The following table indicates the expected node type for the various * different kinds of binding keys: *

*

*

* Note that as explained in {@link IBinding#getKey() IBinding.getkey} * there may be no keys for finding the declaring node for local variables, * local or anonymous classes, etc. *

* * @param key the binding key, or null * @return the corresponding node where a binding with the given * key is declared, or null if the key is null * or if the key does not correspond to a node in this compilation unit * or if bindings were not requested when this AST was built * @see IBinding#getKey() * @since 2.1 */ public ASTNode findDeclaringNode(String key) { return this.ast.getBindingResolver().findDeclaringNode(key); } /** * Returns the internal comment mapper. * * @return the comment mapper, or null if none. * @since 3.0 */ DefaultCommentMapper getCommentMapper() { return this.commentMapper; } /** * Initializes the internal comment mapper with the given * scanner. * * @param scanner the scanner * @since 3.0 */ void initCommentMapper(Scanner scanner) { this.commentMapper = new DefaultCommentMapper(this.optionalCommentTable); this.commentMapper.initialize(this, scanner); } /** * 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 -1 * 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.getStartPosition(); } else { return this.commentMapper.getExtendedStartPosition(node); } } /** * 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 0 * 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 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 class A\n{\n} 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 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 1 if the character * position does not correspond to a source line in the original * source file or if line number information is not known for this * compilation unit * @see ASTParser */ public int lineNumber(int position) { int length = lineEndTable.length; if (length == 0) { // no line number info return 1; } int low = 0; if (position <= lineEndTable[low]) { // position illegal or before the first line delimiter return 1; } // assert position > lineEndTable[low+1] && low == 0 int hi = length - 1; if (position > lineEndTable[hi]) { // position beyond the last line separator if (position >= getStartPosition() + 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) / 2; // assert 0 <= low < mid < hi <= length - 1 if (position <= 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 } } /** * Returns the column number corresponding to the given source character * position in the original source string. Column number are zero-based. * Return zero if it is beyond the valid range. * * @param position a 0-based character position, possibly * negative or out of range * @return the 0-based coloumn number, or 0 if the character * position does not correspond to a source line in the original * source file or if column number information is not known for this * compilation unit * @see ASTParser */ public int columnNumber(final int position) { final int lineNumber = lineNumber(position); final int startOfLine = getPosition(lineNumber, 0); return position - startOfLine; } /** * Given a line number and column number return the corresponding * position in the original source string. * Returns 0 if no line number information is available for this * compilation unit or the requested line number is less than one. * Return the total size of the source string if line * is greater than the actual number lines in the unit. * Assume 0 for the column number if column is less than 0 * or return the position of the last character of the line if column * is beyond the legal range. * * @param line the one-based line number * @param column the zero-based column number. * @return the 0-based character position in the source string. * Return 0 if line/column number information is not known * for this compilation unit or the input are not valid. * */ public int getPosition(int line, int column) { int length = lineEndTable.length; if( length == 0 || line < 1 ) return 0; if( line == 1 ){ final int endOfLine = lineEndTable[0]; return column > endOfLine ? endOfLine : column; } else if( line > length + 1 ) // greater than the number of lines in the source string. if( line > length + 1 ) return getStartPosition() + getLength(); if( column < 0 ) column = 0; // -1 to for one-based to zero-based conversion. // -1, again, to get previous line. final int previousLine = line - 2; final int previousLineOffset = lineEndTable[previousLine]; final int offsetForLine = previousLineOffset + 1 ; // + 1 for the newline character final int currentLineEnd = lineEndTable[line-1]; if( (offsetForLine + column) > currentLineEnd ) return currentLineEnd; else return offsetForLine + column; } /** * The Java element (an org.eclipse.jdt.core.ICompilationUnit or an org.eclipse.jdt.core.IClassFile) * this compilation unit was created from, or null if it was not created from a Java element. * * @return the Java element this compilation unit was created from, or null if none * @since 3.1 */ public IJavaElement getJavaElement() { return this.element; } /** * Returns the list of messages reported by the compiler during the parsing * or the type checking of this compilation unit. This list might be a subset of * errors detected and reported by a Java compiler. *

* This list of messages is suitable for simple clients that do little * more than log the messages or display them to the user. Clients that * need further details should call getProblems to get * compiler problem objects. *

* * @return the list of messages, possibly empty * @see #getProblems() * @see ASTParser */ public Message[] getMessages() { if (this.messages == null) { int problemLength = this.problems.length; if (problemLength == 0) { this.messages = EMPTY_MESSAGES; } else { this.messages = new Message[problemLength]; for (int i = 0; i < problemLength; i++) { IProblem problem = this.problems[i]; int start = problem.getSourceStart(); int end = problem.getSourceEnd(); messages[i] = new Message(problem.getMessage(), start, end - start + 1); } } } return this.messages; } /** * Returns the list of detailed problem reports noted by the compiler * during the parsing or the type checking of this compilation unit. This * list might be a subset of errors detected and reported by a Java * compiler. *

* Simple clients that do little more than log the messages or display * them to the user should probably call getMessages instead. *

* * @return the list of detailed problem objects, possibly empty * @see #getMessages() * @see ASTParser * @since 2.1 */ public IProblem[] getProblems() { return this.problems; } /** * Sets the array of problems reported by the compiler during the parsing or * name resolution of this compilation unit. * * @param problems the list of problems */ void setProblems(IProblem[] problems) { if (problems == null) { throw new IllegalArgumentException(); } this.problems = problems; } /** * Returns a list of the comments encountered while parsing * this compilation unit. *

* Since the Java 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#getJavadoc BodyDeclaration.getJavadoc}. * 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. *

* 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 compilation unit * (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 null, 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 compilation * unit. *

*

* A note on visitors: The only comment nodes that will be visited when * visiting a compilation unit are the doc comments parented by body * declarations. To visit all comments in normal reading order, iterate * over the comment table and call {@link ASTNode#accept(ASTVisitor) accept} * on each element. *

*

* Clients cannot modify the resulting list. *

* * @return an unmodifiable list of comments in increasing order of source * start position, or null if comment information * for this compilation unit is not available * @see ASTParser * @since 3.0 */ public List getCommentList() { return this.optionalCommentList; } /** * Sets the list of the comments encountered while parsing * this compilation unit. * * @param commentTable a list of comments in increasing order * of source start position, or null if comment * information for this compilation unit is not available * @exception IllegalArgumentException if the comment table is * not in increasing order of source position * @see #getCommentList() * @see ASTParser * @since 3.0 */ void setCommentTable(Comment[] commentTable) { // double check table to ensure that all comments have // source positions and are in strictly increasing order if (commentTable == null) { this.optionalCommentList = null; this.optionalCommentTable = null; } else { int nextAvailablePosition = 0; for (int i = 0; i < commentTable.length; i++) { Comment comment = commentTable[i]; if (comment == null) { throw new IllegalArgumentException(); } int start = comment.getStartPosition(); int length = comment.getLength(); if (start < 0 || length < 0 || start < nextAvailablePosition) { throw new IllegalArgumentException(); } nextAvailablePosition = comment.getStartPosition() + comment.getLength(); } this.optionalCommentTable = commentTable; List commentList = Arrays.asList(commentTable); // protect the list from further modification this.optionalCommentList = Collections.unmodifiableList(commentList); } } /** * Sets the Java element (an org.eclipse.jdt.core.ICompilationUnit or an org.eclipse.jdt.core.IClassFile) * this compilation unit was created from, or null if it was not created from a Java element. * * @param element the Java element this compilation unit was created from * @since 3.1 */ void setJavaElement(IJavaElement element) { this.element = element; } /* (omit javadoc for this method) * Method declared on ASTNode. */ int memSize() { int size = BASE_NODE_SIZE + 8 * 4; if (this.lineEndTable != null) { size += HEADERS + 4 * this.lineEndTable.length; } if (this.optionalCommentTable != null) { size += HEADERS + 4 * this.optionalCommentTable.length; } // ignore the space taken up by optionalCommentList return size; } /* (omit javadoc for this method) * Method declared on ASTNode. */ int treeSize() { int size = memSize(); if (this.optionalPackageDeclaration != null) { size += getPackage().treeSize(); } size += this.imports.listSize(); size += this.types.listSize(); // include disconnected comments if (this.optionalCommentList != null) { for (int i = 0; i < this.optionalCommentList.size(); i++) { Comment comment = (Comment) this.optionalCommentList.get(i); if (comment != null && comment.getParent() == null) { size += comment.treeSize(); } } } return size; } /** * Enables the recording of changes to this compilation * unit and its descendents. The compilation unit must have * been created by ASTParser 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 rewrite to get an object * representing the corresponding edits to the original * source code string. * * @exception IllegalArgumentException if this compilation unit is * marked as unmodifiable, or if this compilation unit 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 compilation * unit into an object representing the corresponding text * edits to the given document containing the original source * code for this compilation unit. *

* The compilation unit must have been created by * ASTParser from the source code string in the * given document, and recording must have been turned * on with a prior call to recordModifications * while the AST was still in its original state. *

*

* 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. *

* * @param document original document containing source code * for this compilation unit * @param options the table of formatter options * (key type: String; value type: String); * or null to use the standard global options * {@link org.eclipse.jdt.core.JavaCore#getOptions() JavaCore.getOptions()}. * @return text edit object describing the changes to the * document corresponding to the recorded AST modifications * @exception IllegalArgumentException if the document passed is * null or does not correspond to this AST * @exception IllegalStateException if recordModifications * was not called to enable recording * @see #recordModifications() * @since 3.0 */ public TextEdit rewrite(IDocument document, Map options) { return getAST().rewrite(document, options); } }