1 /*******************************************************************************
2 * Copyright (c) 2004, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.core.dom;
13 import net.sourceforge.phpdt.core.compiler.CharOperation;
14 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
15 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
16 import net.sourceforge.phpdt.internal.compiler.parser.TerminalTokens;
17 import net.sourceforge.phpdt.internal.compiler.util.Util;
20 * Internal class for associating comments with AST nodes.
24 class DefaultCommentMapper {
28 // extended nodes storage
30 ASTNode[] leadingNodes;
31 long[] leadingIndexes;
32 int trailingPtr, lastTrailingPtr;
33 ASTNode[] trailingNodes;
34 long[] trailingIndexes;
35 static final int STORAGE_INCREMENT = 16;
38 * @param table the given table of comments
40 DefaultCommentMapper(Comment[] table) {
41 this.comments = table;
44 boolean hasSameTable(Comment[] table) {
45 return this.comments == table;
49 * Get comment of the list which includes a given position
51 * @param position The position belonging to the looked up comment
52 * @return comment which includes the given position or null if none was found
54 Comment getComment(int position) {
56 if (this.comments == null) {
59 int size = this.comments.length;
63 int index = getCommentIndex(0, position, 0);
67 return this.comments[index];
71 * Get the index of comment which contains given position.
72 * If there's no matching comment, then return depends on exact parameter:
74 * < 0: return index of the comment before the given position
75 * > 0: return index of the comment after the given position
77 private int getCommentIndex(int start, int position, int exact) {
79 if (this.comments.length > 0 && this.comments[0].getStartPosition() == 0) {
84 int bottom = start, top = this.comments.length - 1;
85 int i = 0, index = -1;
86 Comment comment = null;
87 while (bottom <= top) {
88 i = bottom + (top - bottom) /2;
89 comment = this.comments[i];
90 int commentStart = comment.getStartPosition();
91 if (position < commentStart) {
93 } else if (position >=(commentStart+comment.getLength())) {
100 if (index<0 && exact!=0) {
101 comment = this.comments[i];
102 if (position < comment.getStartPosition()) {
103 return exact<0 ? i-1 : i;
105 return exact<0 ? i : i+1;
112 * Returns the extended start position of the given node. Unlike
113 * {@link ASTNode#getStartPosition()} and {@link ASTNode#getLength()},
114 * the extended source range may include comments and whitespace
115 * immediately before or after the normal source range for the node.
117 * @param node the node
118 * @return the 0-based character index, or <code>-1</code>
119 * if no source position information is recorded for this node
120 * @see #getExtendedLength(ASTNode)
123 public int getExtendedStartPosition(ASTNode node) {
124 if (this.leadingPtr >= 0) {
126 for (int i=0; range<0 && i<=this.leadingPtr; i++) {
127 if (this.leadingNodes[i] == node) range = this.leadingIndexes[i];
130 return this.comments[(int)(range>>32)].getStartPosition() ;
133 return node.getStartPosition();
137 * Search the line number corresponding to a specific position
138 * between the given line range (inclusive)
139 * @param position int
140 * @parem lineRange size-2 int[]
143 public final int getLineNumber(int position, int[] lineRange) {
144 int[] lineEnds = this.scanner.lineEnds;
145 int length = lineEnds.length;
146 return Util.getLineNumber(position, lineEnds, (lineRange[0] > length ? length : lineRange[0]) -1, (lineRange[1] > length ? length : lineRange[1]) - 1);
150 * Returns the extended end position of the given node.
152 public int getExtendedEnd(ASTNode node) {
153 int end = node.getStartPosition() + node.getLength();
154 if (this.trailingPtr >= 0) {
156 for (int i=0; range<0 && i<=this.trailingPtr; i++) {
157 if (this.trailingNodes[i] == node) range = this.trailingIndexes[i];
160 Comment lastComment = this.comments[(int) range];
161 end = lastComment.getStartPosition() + lastComment.getLength();
168 * Returns the extended source length of the given node. Unlike
169 * {@link ASTNode#getStartPosition()} and {@link ASTNode#getLength()},
170 * the extended source range may include comments and whitespace
171 * immediately before or after the normal source range for the node.
173 * @param node the node
174 * @return a (possibly 0) length, or <code>0</code>
175 * if no source position information is recorded for this node
176 * @see #getExtendedStartPosition(ASTNode)
177 * @see #getExtendedEnd(ASTNode)
180 public int getExtendedLength(ASTNode node) {
181 return getExtendedEnd(node) - getExtendedStartPosition(node) + 1;
185 * Return index of first leading comment of a given node.
188 * @return index of first leading comment or -1 if node has no leading comment
190 int firstLeadingCommentIndex(ASTNode node) {
191 if (this.leadingPtr >= 0) {
192 for (int i=0; i<=this.leadingPtr; i++) {
193 if (this.leadingNodes[i] == node) {
194 return (int) (this.leadingIndexes[i]>>32);
202 * Return index of last trailing comment of a given node.
205 * @return index of last trailing comment or -1 if node has no trailing comment
207 int lastTrailingCommentIndex(ASTNode node) {
208 if (this.trailingPtr >= 0) {
209 for (int i=0; i<=this.trailingPtr; i++) {
210 if (this.trailingNodes[i] == node) {
211 return (int) this.trailingIndexes[i];
219 * Initialize leading and trailing comments tables in whole nodes hierarchy of a compilation
221 * Scanner is necessary to scan between nodes and comments and verify if there's
222 * nothing else than white spaces.
224 void initialize(CompilationUnit unit, Scanner sc) {
226 // Init array pointers
227 this.leadingPtr = -1;
228 this.trailingPtr = -1;
231 this.comments = unit.optionalCommentTable;
232 if (this.comments == null) {
235 int size = this.comments.length;
240 // Init scanner and start ranges computing
242 this.scanner.tokenizeWhiteSpace = true;
245 DefaultASTVisitor commentVisitor = new CommentMapperVisitor();
246 unit.accept(commentVisitor);
248 // Reduce leading arrays if necessary
249 int leadingCount = this.leadingPtr + 1;
250 if (leadingCount > 0 && leadingCount < this.leadingIndexes.length) {
251 System.arraycopy(this.leadingNodes, 0, this.leadingNodes = new ASTNode[leadingCount], 0, leadingCount);
252 System.arraycopy(this.leadingIndexes, 0, this.leadingIndexes= new long[leadingCount], 0, leadingCount);
255 // Reduce trailing arrays if necessary
256 if (this.trailingPtr >= 0) {
257 // remove last remaining unresolved nodes
258 while (this.trailingIndexes[this.trailingPtr] == -1) {
260 if (this.trailingPtr < 0) {
261 this.trailingIndexes = null;
262 this.trailingNodes = null;
268 int trailingCount = this.trailingPtr + 1;
269 if (trailingCount > 0 && trailingCount < this.trailingIndexes.length) {
270 System.arraycopy(this.trailingNodes, 0, this.trailingNodes = new ASTNode[trailingCount], 0, trailingCount);
271 System.arraycopy(this.trailingIndexes, 0, this.trailingIndexes= new long[trailingCount], 0, trailingCount);
275 // Release scanner as it's only used during unit visit
280 * Search and store node leading comments. Comments are searched in position range
281 * from previous extended position to node start position. If one or several comment are found,
282 * returns first comment start position, otherwise returns node start position.
284 * Starts to search for first comment before node start position and return if none was found...
286 * When first comment is found before node, goes up in comment list until one of
287 * following conditions becomes true:
289 * <li>comment end is before previous end</li>
290 * <li>comment start and previous end is on the same line but not on same line of node start</li>
291 * <li>there's other than white characters between current node and comment</li>
292 * <li>there's more than 1 line between current node and comment</li>
294 * If some comment have been found, then no token should be on
295 * on the same line before, so remove all comments which do not verify this assumption.
297 * If finally there's leading still comments, then stores indexes of the first and last one
298 * in leading comments table.
300 int storeLeadingComments(ASTNode node, int previousEnd, int[] parentLineRange) {
301 // Init extended position
302 int nodeStart = node.getStartPosition();
303 int extended = nodeStart;
305 // Get line of node start position
306 int previousEndLine = getLineNumber(previousEnd, parentLineRange);
307 int nodeStartLine = getLineNumber(nodeStart, parentLineRange);
309 // Find first comment index
310 int idx = getCommentIndex(0, nodeStart, -1);
315 // Look after potential comments
318 int previousStart = nodeStart;
319 while (idx >= 0 && previousStart >= previousEnd) {
320 // Verify for each comment that there's only white spaces between end and start of {following comment|node}
321 Comment comment = this.comments[idx];
322 int commentStart = comment.getStartPosition();
323 int end = commentStart+comment.getLength()-1;
324 int commentLine = getLineNumber(commentStart, parentLineRange);
325 if (end <= previousEnd || (commentLine == previousEndLine && commentLine != nodeStartLine)) {
326 // stop search on condition 1) and 2)
328 } else if ((end+1) < previousStart) { // may be equals => then no scan is necessary
329 this.scanner.resetTo(end+1, previousStart);
331 int token = this.scanner.getNextToken();
332 if (token != TerminalTokens.TokenNameWHITESPACE || this.scanner.currentPosition != previousStart) {
333 // stop search on condition 3)
334 // if first comment fails, then there's no extended position in fact
340 } catch (InvalidInputException e) {
341 // Should not happen, but return no extended position...
344 // verify that there's no more than one line between node/comments
345 char[] gap = this.scanner.getCurrentIdentifierSource();
348 while ((pos=CharOperation.indexOf('\n', gap,pos+1)) >= 0) {
352 // stop search on condition 4)
356 // Store previous infos
357 previousStart = commentStart;
360 if (startIdx != -1) {
361 // Verify that there's no token on the same line before first leading comment
362 int commentStart = this.comments[startIdx].getStartPosition();
363 if (previousEnd < commentStart && previousEndLine != nodeStartLine) {
364 int lastTokenEnd = previousEnd;
365 this.scanner.resetTo(previousEnd, commentStart);
367 while (this.scanner.currentPosition < commentStart) {
368 if (this.scanner.getNextToken() != TerminalTokens.TokenNameWHITESPACE) {
369 lastTokenEnd = this.scanner.getCurrentTokenEndPosition();
372 } catch (InvalidInputException e) {
375 int lastTokenLine = getLineNumber(lastTokenEnd, parentLineRange);
376 int length = this.comments.length;
377 while (startIdx<length && lastTokenLine == getLineNumber(this.comments[startIdx].getStartPosition(), parentLineRange) && nodeStartLine != lastTokenLine) {
381 // Store leading comments indexes
382 if (startIdx <= endIdx) {
383 if (++this.leadingPtr == 0) {
384 this.leadingNodes = new ASTNode[STORAGE_INCREMENT];
385 this.leadingIndexes = new long[STORAGE_INCREMENT];
386 } else if (this.leadingPtr == this.leadingNodes.length) {
387 int newLength = (this.leadingPtr*3/2)+STORAGE_INCREMENT;
388 System.arraycopy(this.leadingNodes, 0, this.leadingNodes = new ASTNode[newLength], 0, this.leadingPtr);
389 System.arraycopy(this.leadingIndexes, 0, this.leadingIndexes = new long[newLength], 0, this.leadingPtr);
391 this.leadingNodes[this.leadingPtr] = node;
392 this.leadingIndexes[this.leadingPtr] = (((long)startIdx)<<32) + endIdx;
393 extended = this.comments[endIdx].getStartPosition();
400 * Search and store node trailing comments. Comments are searched in position range
401 * from node end position to specified next start. If one or several comment are found,
402 * returns last comment end position, otherwise returns node end position.
404 * Starts to search for first comment after node end position and return if none was found...
406 * When first comment is found after node, goes down in comment list until one of
407 * following conditions becomes true:
409 * <li>comment start is after next start</li>
410 * <li>there's other than white characters between current node and comment</li>
411 * <li>there's more than 1 line between current node and comment</li>
413 * If at least potential comments have been found, then all of them has to be separated
414 * from following node. So, remove all comments which do not verify this assumption.
415 * Note that this verification is not applicable on last node.
417 * If finally there's still trailing comments, then stores indexes of the first and last one
418 * in trailing comments table.
420 int storeTrailingComments(ASTNode node, int nextStart, boolean lastChild, int[] parentLineRange) {
422 // Init extended position
423 int nodeEnd = node.getStartPosition()+node.getLength()-1;
424 if (nodeEnd == nextStart) {
425 // special case for last child of its parent
426 if (++this.trailingPtr == 0) {
427 this.trailingNodes = new ASTNode[STORAGE_INCREMENT];
428 this.trailingIndexes = new long[STORAGE_INCREMENT];
429 this.lastTrailingPtr = -1;
430 } else if (this.trailingPtr == this.trailingNodes.length) {
431 int newLength = (this.trailingPtr*3/2)+STORAGE_INCREMENT;
432 System.arraycopy(this.trailingNodes, 0, this.trailingNodes = new ASTNode[newLength], 0, this.trailingPtr);
433 System.arraycopy(this.trailingIndexes, 0, this.trailingIndexes = new long[newLength], 0, this.trailingPtr);
435 this.trailingNodes[this.trailingPtr] = node;
436 this.trailingIndexes[this.trailingPtr] = -1;
439 int extended = nodeEnd;
442 int nodeEndLine = getLineNumber(nodeEnd, parentLineRange);
444 // Find comments range index
445 int idx = getCommentIndex(0, nodeEnd, 1);
450 // Look after potential comments
453 int length = this.comments.length;
454 int commentStart = extended+1;
455 int previousEnd = nodeEnd+1;
456 int sameLineIdx = -1;
457 while (idx<length && commentStart < nextStart) {
458 // get comment and leave if next starting position has been reached
459 Comment comment = this.comments[idx];
460 commentStart = comment.getStartPosition();
461 // verify that there's nothing else than white spaces between node/comments
462 if (commentStart >= nextStart) {
463 // stop search on condition 1)
465 } else if (previousEnd < commentStart) {
466 this.scanner.resetTo(previousEnd, commentStart);
468 int token = this.scanner.getNextToken();
469 if (token != TerminalTokens.TokenNameWHITESPACE || this.scanner.currentPosition != commentStart) {
470 // stop search on condition 2)
471 // if first index fails, then there's no extended position in fact...
472 if (idx == startIdx) {
475 // otherwise we get the last index of trailing comment => break
478 } catch (InvalidInputException e) {
479 // Should not happen, but return no extended position...
482 // verify that there's no more than one line between node/comments
483 char[] gap = this.scanner.getCurrentIdentifierSource();
486 while ((pos=CharOperation.indexOf('\n', gap,pos+1)) >= 0) {
490 // stop search on condition 3)
494 // Store index if we're on the same line than node end
495 int commentLine = getLineNumber(commentStart, parentLineRange);
496 if (commentLine == nodeEndLine) {
499 // Store previous infos
500 previousEnd = commentStart+comment.getLength();
504 // Verify that following node start is separated
506 int nextLine = getLineNumber(nextStart, parentLineRange);
507 int previousLine = getLineNumber(previousEnd, parentLineRange);
508 if((nextLine - previousLine) <= 1) {
509 if (sameLineIdx == -1) return nodeEnd;
510 endIdx = sameLineIdx;
513 // Store trailing comments indexes
514 if (++this.trailingPtr == 0) {
515 this.trailingNodes = new ASTNode[STORAGE_INCREMENT];
516 this.trailingIndexes = new long[STORAGE_INCREMENT];
517 this.lastTrailingPtr = -1;
518 } else if (this.trailingPtr == this.trailingNodes.length) {
519 int newLength = (this.trailingPtr*3/2)+STORAGE_INCREMENT;
520 System.arraycopy(this.trailingNodes, 0, this.trailingNodes = new ASTNode[newLength], 0, this.trailingPtr);
521 System.arraycopy(this.trailingIndexes, 0, this.trailingIndexes = new long[newLength], 0, this.trailingPtr);
523 this.trailingNodes[this.trailingPtr] = node;
524 long nodeRange = (((long)startIdx)<<32) + endIdx;
525 this.trailingIndexes[this.trailingPtr] = nodeRange;
526 // Compute new extended end
527 extended = this.comments[endIdx].getStartPosition()+this.comments[endIdx].getLength()-1;
528 // Look for children unresolved extended end
529 ASTNode previousNode = node;
530 int ptr = this.trailingPtr - 1; // children extended end were stored before
532 long range = this.trailingIndexes[ptr];
533 if (range != -1) break; // there's no more unresolved nodes
534 ASTNode unresolved = this.trailingNodes[ptr];
535 if (previousNode != unresolved.getParent()) break; // we're no longer in node ancestor hierarchy
536 this.trailingIndexes[ptr] = nodeRange;
537 previousNode = unresolved;
538 ptr--; // get previous node
540 // Remove remaining unresolved nodes
541 if (ptr > this.lastTrailingPtr) {
542 int offset = ptr - this.lastTrailingPtr;
543 for (int i=ptr+1; i<=this.trailingPtr; i++) {
544 this.trailingNodes[i-offset] = this.trailingNodes[i];
545 this.trailingIndexes[i-offset] = this.trailingIndexes[i];
547 this.trailingPtr -= offset;
549 this.lastTrailingPtr = this.trailingPtr;
554 class CommentMapperVisitor extends DefaultASTVisitor {
556 ASTNode topSiblingParent = null;
557 ASTNode[] siblings = new ASTNode[10];
558 int[][] parentLineRange = new int[10][];
561 protected boolean visitNode(ASTNode node) {
563 // Get default previous end
564 ASTNode parent = node.getParent();
565 int previousEnd = parent.getStartPosition();
567 // Look for sibling node
568 ASTNode sibling = parent == this.topSiblingParent ? (ASTNode) this.siblings[this.siblingPtr] : null;
569 if (sibling != null) {
570 // Found one previous sibling, so compute its trailing comments using current node start position
572 previousEnd = storeTrailingComments(sibling, node.getStartPosition(), false, this.parentLineRange[this.siblingPtr]);
573 } catch (Exception ex) {
574 // Give up extended ranges at this level if unexpected exception happens...
578 // Stop visit for malformed node (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=84049)
579 if ((node.typeAndFlags & ASTNode.MALFORMED) != 0) {
583 // Compute leading comments for current node
584 int[] previousLineRange = this.siblingPtr > -1 ? this.parentLineRange[this.siblingPtr] : new int[] {1, DefaultCommentMapper.this.scanner.linePtr+1};
586 storeLeadingComments(node, previousEnd, previousLineRange);
587 } catch (Exception ex) {
588 // Give up extended ranges at this level if unexpected exception happens...
591 // Store current node as waiting sibling for its parent
592 if (this.topSiblingParent != parent) {
593 if (this.siblings.length == ++this.siblingPtr) {
594 System.arraycopy(this.siblings, 0, this.siblings = new ASTNode[this.siblingPtr*2], 0, this.siblingPtr);
595 System.arraycopy(this.parentLineRange, 0, this.parentLineRange = new int[this.siblingPtr*2][], 0, this.siblingPtr);
597 if (this.topSiblingParent == null) {
598 // node is a CompilationUnit
599 this.parentLineRange[this.siblingPtr] = previousLineRange;
601 int parentStart = parent.getStartPosition();
602 int firstLine = getLineNumber(parentStart, previousLineRange);
603 int lastLine = getLineNumber(parentStart + parent.getLength() - 1, previousLineRange);
604 if (this.parentLineRange[this.siblingPtr] == null) {
605 this.parentLineRange[this.siblingPtr] = new int[] {firstLine, lastLine};
607 int[] lineRange = this.parentLineRange[this.siblingPtr];
608 lineRange[0] = firstLine;
609 lineRange[1] = lastLine;
612 this.topSiblingParent = parent;
614 this.siblings[this.siblingPtr] = node;
616 // We're always ok to visit sub-levels
620 protected void endVisitNode(ASTNode node) {
622 // Look if a child node is waiting for trailing comments computing
623 ASTNode sibling = this.topSiblingParent == node ? (ASTNode) this.siblings[this.siblingPtr] : null;
624 if (sibling != null) {
626 storeTrailingComments(sibling, node.getStartPosition()+node.getLength()-1, true, this.parentLineRange[this.siblingPtr]);
627 } catch (Exception ex) {
628 // Give up extended ranges at this level if unexpected exception happens...
631 // Remove sibling if needed
632 if (this.topSiblingParent != null /*not a CompilationUnit*/
633 && this.topSiblingParent == node) {
635 this.topSiblingParent = node.getParent();
639 public boolean visit ( CompilationUnit node) {
640 // do nothing special, just go down in sub-levels