All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jdt.core.dom.DefaultCommentMapper Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2004, 2011 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 org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.internal.compiler.parser.Scanner;
import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
import org.eclipse.jdt.internal.compiler.util.Util;

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

	// extended nodes storage
	int leadingPtr;
	ASTNode[] leadingNodes;
	long[] leadingIndexes;
	int trailingPtr, lastTrailingPtr;
	ASTNode[] trailingNodes;
	long[] trailingIndexes;
	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].getStartPosition() == 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.getStartPosition();
			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.getStartPosition()) {
				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#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 (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)].getStartPosition() ;
			}
		}
		return node.getStartPosition();
	}

	/*
	 * Search the line number corresponding to a specific position
	 * between the given line range (inclusive)
	 * @param position int
	 * @parem lineRange size-2 int[]
	 * @return int
	 */
	public final int getLineNumber(int position, int[] lineRange) {
		int[] lineEnds = this.scanner.lineEnds;
		int length = lineEnds.length;
		return Util.getLineNumber(position, lineEnds, (lineRange[0] > length ? length : lineRange[0]) -1, (lineRange[1] > length ? length : lineRange[1]) - 1);
	}

	/*
	 * Returns the extended end position of the given node.
	 */
	public int getExtendedEnd(ASTNode node) {
		int end = node.getStartPosition() + 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.getStartPosition() + lastComment.getLength();
			}
		}
		return end-1;
	}

	/**
	 * 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)
	 * @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(CompilationUnit unit, Scanner sc) {

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

		// Init comments
		this.comments = unit.optionalCommentTable;
		if (this.comments == null) {
			return;
		}
		int size = this.comments.length;
		if (size == 0) {
			return;
		}

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

		// Start unit visit
		DefaultASTVisitor 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.
	 * 

* Starts to search for first comment before node start position and return if none was found... *

* When first comment is found before node, goes up in comment list until one of * following conditions becomes true: *

    *
  1. comment end is before previous end
  2. *
  3. comment start and previous end is on the same line but not on same line of node start
  4. *
  5. there's other than white characters between current node and comment
  6. *
  7. there's more than 1 line between current node and comment
  8. *
* 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. *

* 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.getStartPosition(); 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.getStartPosition(); 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 this.scanner.resetTo(end+1, previousStart); try { int token = this.scanner.getNextToken(); if (token != TerminalTokens.TokenNameWHITESPACE || this.scanner.currentPosition != previousStart) { // stop search on condition 3) // if first comment fails, then there's no extended position in fact if (idx == endIdx) { return nodeStart; } break; } } catch (InvalidInputException e) { // Should not happen, but return no extended position... return nodeStart; } // verify that there's no more than one line between node/comments char[] gap = this.scanner.getCurrentIdentifierSource(); int nbrLine = 0; int pos = -1; while ((pos=CharOperation.indexOf('\n', gap,pos+1)) >= 0) { nbrLine++; } if (nbrLine > 1) { // stop search on condition 4) break; } } // Store previous infos previousStart = commentStart; startIdx = idx--; } if (startIdx != -1) { // Verify that there's no token on the same line before first leading comment int commentStart = this.comments[startIdx].getStartPosition(); if (previousEnd < commentStart && previousEndLine != nodeStartLine) { int lastTokenEnd = previousEnd; this.scanner.resetTo(previousEnd, commentStart); try { while (this.scanner.currentPosition < commentStart) { if (this.scanner.getNextToken() != TerminalTokens.TokenNameWHITESPACE) { lastTokenEnd = this.scanner.getCurrentTokenEndPosition(); } } } catch (InvalidInputException e) { // do nothing } int lastTokenLine = getLineNumber(lastTokenEnd, parentLineRange); int length = this.comments.length; while (startIdx * Starts to search for first comment after node end position and return if none was found... *

* When first comment is found after node, goes down in comment list until one of * following conditions becomes true: *

    *
  1. comment start is after next start
  2. *
  3. there's other than white characters between current node and comment
  4. *
  5. there's more than 1 line between current node and comment
  6. *
* 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. *

* 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.getStartPosition()+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= nextStart) { // stop search on condition 1) break; } else if (previousEnd < commentStart) { this.scanner.resetTo(previousEnd, commentStart); try { int token = this.scanner.getNextToken(); if (token != TerminalTokens.TokenNameWHITESPACE || this.scanner.currentPosition != commentStart) { // 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 (InvalidInputException e) { // Should not happen, but return no extended position... return nodeEnd; } // verify that there's no more than one line between node/comments char[] gap = this.scanner.getCurrentIdentifierSource(); int nbrLine = 0; int pos = -1; while ((pos=CharOperation.indexOf('\n', gap,pos+1)) >= 0) { nbrLine++; } if (nbrLine > 1) { // stop search on condition 3) break; } } // 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].getStartPosition()+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 DefaultASTVisitor { ASTNode topSiblingParent = null; ASTNode[] siblings = new ASTNode[10]; int[][] parentLineRange = new int[10][]; int siblingPtr = -1; protected boolean visitNode(ASTNode node) { // Get default previous end ASTNode parent = node.getParent(); int previousEnd = parent.getStartPosition(); // 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.getStartPosition(), 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.typeAndFlags & ASTNode.MALFORMED) != 0) { return false; } // Compute leading comments for current node int[] previousLineRange = this.siblingPtr > -1 ? this.parentLineRange[this.siblingPtr] : new int[] {1, DefaultCommentMapper.this.scanner.linePtr+1}; 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.getStartPosition(); 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; } protected 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.getStartPosition()+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 (Modifier modifier) { // we don't want to map comment to the modifier return false; } public boolean visit ( CompilationUnit node) { // do nothing special, just go down in sub-levels return true; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy