94c479fbf174c1a0589deba9a7c24977d243cf08
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / core / dom / DefaultCommentMapper.java
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
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.core.dom;
12
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;
18
19 /**
20  * Internal class for associating comments with AST nodes.
21  * 
22  * @since 3.0
23  */
24 class DefaultCommentMapper {
25         Comment[] comments;
26         Scanner scanner;
27         
28         // extended nodes storage
29         int leadingPtr;
30         ASTNode[] leadingNodes;
31         long[] leadingIndexes;
32         int trailingPtr, lastTrailingPtr;
33         ASTNode[] trailingNodes;
34         long[] trailingIndexes;
35         static final int STORAGE_INCREMENT = 16;
36
37         /**
38          * @param table the given table of comments
39          */
40         DefaultCommentMapper(Comment[] table) {
41                 this.comments = table;
42         }
43
44         boolean hasSameTable(Comment[] table) {
45                 return this.comments == table;
46         }
47
48         /**
49          * Get comment of the list which includes a given position
50          * 
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
53          */
54         Comment getComment(int position) {
55
56                 if (this.comments == null) {
57                         return null;
58                 }
59                 int size = this.comments.length;
60                 if (size == 0) {
61                         return null;
62                 }
63                 int index = getCommentIndex(0, position, 0);
64                 if (index<0) {
65                         return null;
66                 }
67                 return this.comments[index];
68         }
69
70         /*
71          * Get the index of comment which contains given position.
72          * If there's no matching comment, then return depends on exact parameter:
73          *              = 0: return -1
74          *              < 0: return index of the comment before the given position
75          *              > 0: return index of the comment after the given position
76          */
77         private int getCommentIndex(int start, int position, int exact) {
78                 if (position == 0) {
79                         if (this.comments.length > 0 && this.comments[0].getStartPosition() == 0) {
80                                 return 0;
81                         }
82                         return -1;
83                 }
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) {
92                                 top = i-1;
93                         } else if (position >=(commentStart+comment.getLength())) {
94                                 bottom = i+1;
95                         } else {
96                                 index = i;
97                                 break;
98                         }
99                 }
100                 if (index<0 && exact!=0) {
101                         comment = this.comments[i];
102                         if (position < comment.getStartPosition()) {
103                                 return exact<0 ? i-1 : i;
104                         } else {
105                                 return exact<0 ? i : i+1;
106                         }
107                 }
108                 return index;
109         }
110
111         /**
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.
116          * 
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)
121          * @since 3.0
122          */
123         public int getExtendedStartPosition(ASTNode node) {
124                 if (this.leadingPtr >= 0) {
125                         long range = -1;
126                         for (int i=0; range<0 && i<=this.leadingPtr; i++) {
127                                 if (this.leadingNodes[i] == node) range = this.leadingIndexes[i];
128                         }
129                         if (range >= 0) {
130                                 return  this.comments[(int)(range>>32)].getStartPosition() ;
131                         }
132                 }
133                 return node.getStartPosition();
134         }
135
136         /*
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[]
141          * @return int
142          */
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);
147         }
148
149         /*
150          * Returns the extended end position of the given node.
151          */
152         public int getExtendedEnd(ASTNode node) {
153                 int end = node.getStartPosition() + node.getLength();
154                 if (this.trailingPtr >= 0) {
155                         long range = -1;
156                         for (int i=0; range<0 && i<=this.trailingPtr; i++) {
157                                 if (this.trailingNodes[i] == node) range = this.trailingIndexes[i];
158                         }
159                         if (range >= 0) {
160                                 Comment lastComment = this.comments[(int) range];
161                                 end = lastComment.getStartPosition() + lastComment.getLength();
162                         }
163                 }
164                 return end-1;
165         }
166
167         /**
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.
172          * 
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)
178          * @since 3.0
179          */
180         public int getExtendedLength(ASTNode node) {
181                 return getExtendedEnd(node) - getExtendedStartPosition(node) + 1;
182         }
183
184         /**
185          * Return index of first leading comment of a given node.
186          * 
187          * @param node
188          * @return index of first leading comment or -1 if node has no leading comment
189          */
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);
195                                 }
196                         }
197                 }
198                 return -1;
199         }
200
201         /**
202          * Return index of last trailing comment of a given node.
203          * 
204          * @param node
205          * @return index of last trailing comment or -1 if node has no trailing comment
206          */
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];
212                                 }
213                         }
214                 }
215                 return -1;
216         }
217
218         /*
219          * Initialize leading and trailing comments tables in whole nodes hierarchy of a compilation
220          * unit.
221          * Scanner is necessary to scan between nodes and comments and verify if there's
222          * nothing else than white spaces.
223          */
224         void initialize(CompilationUnit unit, Scanner sc) {
225
226                 // Init array pointers
227                 this.leadingPtr = -1;
228                 this.trailingPtr = -1;
229         
230                 // Init comments
231                 this.comments = unit.optionalCommentTable;
232                 if (this.comments == null) {
233                         return;
234                 }
235                 int size = this.comments.length;
236                 if (size == 0) {
237                         return;
238                 }
239
240                 // Init scanner and start ranges computing
241                 this.scanner = sc;
242                 this.scanner.tokenizeWhiteSpace = true;
243
244                 // Start unit visit
245                 DefaultASTVisitor commentVisitor = new CommentMapperVisitor();
246                 unit.accept(commentVisitor);
247                 
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);
253                 }
254                 
255                 // Reduce trailing arrays if necessary
256                 if (this.trailingPtr >= 0) {
257                         // remove last remaining unresolved nodes
258                         while (this.trailingIndexes[this.trailingPtr] == -1) {
259                                 this.trailingPtr--;
260                                 if (this.trailingPtr < 0) {
261                                         this.trailingIndexes = null;
262                                         this.trailingNodes = null;
263                                         break;
264                                 }
265                         }
266
267                         // reduce array size
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);
272                         }
273                 }
274
275                 // Release scanner as it's only used during unit visit
276                 this.scanner = null;
277         }
278
279         /**
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.
283          * <p>
284          * Starts to search for first comment before node start position and return if none was found...
285          *</p><p>
286          * When first comment is found before node, goes up in comment list until one of
287          * following conditions becomes true:
288          * <ol>
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>
293          * </ol>
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.
296          * </p><p>
297          * If finally there's leading still comments, then stores indexes of the first and last one
298          * in leading comments table.
299          */
300         int storeLeadingComments(ASTNode node, int previousEnd, int[] parentLineRange) {
301                 // Init extended position
302                 int nodeStart = node.getStartPosition();
303                 int extended = nodeStart;
304                 
305                 // Get line of node start position
306                 int previousEndLine = getLineNumber(previousEnd, parentLineRange);
307                 int nodeStartLine = getLineNumber(nodeStart, parentLineRange);
308                 
309                 // Find first comment index
310                 int idx = getCommentIndex(0, nodeStart, -1);
311                 if (idx == -1) {
312                         return nodeStart;
313                 }
314                 
315                 // Look after potential comments
316                 int startIdx = -1;
317                 int endIdx = idx;
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)
327                                 break;
328                         } else if ((end+1) < previousStart) { // may be equals => then no scan is necessary
329                                 this.scanner.resetTo(end+1, previousStart);
330                                 try {
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
335                                                 if (idx == endIdx) {
336                                                         return nodeStart;
337                                                 }
338                                                 break;
339                                         }
340                                 } catch (InvalidInputException e) {
341                                         // Should not happen, but return no extended position...
342                                         return nodeStart;
343                                 }
344                                 // verify that there's no more than one line between node/comments
345                                 char[] gap = this.scanner.getCurrentIdentifierSource();
346                                 int nbrLine = 0;
347                                 int pos = -1;
348                                 while ((pos=CharOperation.indexOf('\n', gap,pos+1)) >= 0) {
349                                         nbrLine++;
350                                 }
351                                 if (nbrLine > 1) {
352                                         // stop search on condition 4)
353                                         break;
354                                 }
355                         }
356                         // Store previous infos
357                         previousStart = commentStart;
358                         startIdx = idx--;
359                 }
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);
366                                 try {
367                                         while (this.scanner.currentPosition < commentStart) {
368                                                 if (this.scanner.getNextToken() != TerminalTokens.TokenNameWHITESPACE) {
369                                                         lastTokenEnd =  this.scanner.getCurrentTokenEndPosition();
370                                                 }
371                                         }
372                                 } catch (InvalidInputException e) {
373                                         // do nothing
374                                 }
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) {
378                                         startIdx++;
379                                 }
380                         }
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);
390                                 }
391                                 this.leadingNodes[this.leadingPtr] = node;
392                                 this.leadingIndexes[this.leadingPtr] = (((long)startIdx)<<32) + endIdx;
393                                 extended = this.comments[endIdx].getStartPosition();
394                         }
395                 }
396                 return extended;
397         }
398
399         /**
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.
403          * <p>
404          * Starts to search for first comment after node end position and return if none was found...
405          *</p><p>
406          * When first comment is found after node, goes down in comment list until one of
407          * following conditions becomes true:
408          * <ol>
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>
412          *</ol>
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.
416          * </p><p>
417          * If finally there's still trailing comments, then stores indexes of the first and last one
418          * in trailing comments table.
419          */
420         int storeTrailingComments(ASTNode node, int nextStart,  boolean lastChild, int[] parentLineRange) {
421
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);
434                         }
435                         this.trailingNodes[this.trailingPtr] = node;
436                         this.trailingIndexes[this.trailingPtr] = -1;
437                         return nodeEnd;
438                 }
439                 int extended = nodeEnd;
440                 
441                 // Get line number
442                 int nodeEndLine = getLineNumber(nodeEnd, parentLineRange);
443                 
444                 // Find comments range index
445                 int idx = getCommentIndex(0, nodeEnd, 1);
446                 if (idx == -1) {
447                         return nodeEnd;
448                 }
449
450                 // Look after potential comments
451                 int startIdx = idx;
452                 int endIdx = -1;
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)
464                                 break;
465                         } else if (previousEnd < commentStart) {
466                                 this.scanner.resetTo(previousEnd, commentStart);
467                                 try {
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) {
473                                                         return nodeEnd;
474                                                 }
475                                                 // otherwise we get the last index of trailing comment => break
476                                                 break;
477                                         }
478                                 } catch (InvalidInputException e) {
479                                         // Should not happen, but return no extended position...
480                                         return nodeEnd;
481                                 }
482                                 // verify that there's no more than one line between node/comments
483                                 char[] gap = this.scanner.getCurrentIdentifierSource();
484                                 int nbrLine = 0;
485                                 int pos = -1;
486                                 while ((pos=CharOperation.indexOf('\n', gap,pos+1)) >= 0) {
487                                         nbrLine++;
488                                 }
489                                 if (nbrLine > 1) {
490                                         // stop search on condition 3)
491                                         break;
492                                 }
493                         }
494                         // Store index if we're on the same line than node end
495                         int commentLine = getLineNumber(commentStart, parentLineRange);
496                         if (commentLine == nodeEndLine) {
497                                 sameLineIdx = idx;
498                         }
499                         // Store previous infos
500                         previousEnd = commentStart+comment.getLength();
501                         endIdx = idx++;
502                 }
503                 if (endIdx != -1) {
504                         // Verify that following node start is separated
505                         if (!lastChild) {
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;
511                                 }
512                         }
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);
522                         }
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
531                         while (ptr >= 0) {
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
539                         }
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];
546                                 }
547                                 this.trailingPtr -= offset;
548                         }
549                         this.lastTrailingPtr = this.trailingPtr;
550                 }
551                 return extended;
552         }
553
554         class CommentMapperVisitor extends DefaultASTVisitor {
555
556                 ASTNode topSiblingParent = null;
557                 ASTNode[] siblings = new ASTNode[10];
558                 int[][] parentLineRange = new int[10][];
559                 int siblingPtr = -1;
560
561                 protected boolean visitNode(ASTNode node) {
562
563                         // Get default previous end
564                         ASTNode parent = node.getParent();
565                         int previousEnd = parent.getStartPosition();
566                         
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
571                                 try {
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...
575                                 }
576                         }
577
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) {
580                                 return false;
581                         }
582
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};
585                         try {
586                                 storeLeadingComments(node, previousEnd, previousLineRange);
587                         } catch (Exception ex) {
588                                 // Give up extended ranges at this level if unexpected exception happens...
589                         }
590                         
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);
596                                 }
597                                 if (this.topSiblingParent == null) {
598                                         // node is a CompilationUnit
599                                         this.parentLineRange[this.siblingPtr] = previousLineRange;
600                                 } else {
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};
606                                         } else {
607                                                 int[] lineRange = this.parentLineRange[this.siblingPtr];
608                                                 lineRange[0] = firstLine;
609                                                 lineRange[1] = lastLine;
610                                         }
611                                 }
612                                 this.topSiblingParent = parent;
613                         }
614                         this.siblings[this.siblingPtr] = node;
615
616                         // We're always ok to visit sub-levels
617                         return true;
618                 }
619                 
620                 protected void endVisitNode(ASTNode node) {
621
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) {
625                                 try {
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...
629                                 }
630                         }
631                         // Remove sibling if needed
632                         if (this.topSiblingParent != null /*not a CompilationUnit*/
633                                         && this.topSiblingParent == node) {
634                                 this.siblingPtr--;
635                                 this.topSiblingParent = node.getParent();
636                         }
637                 }
638
639                 public boolean visit ( CompilationUnit node) {
640                         // do nothing special, just go down in sub-levels
641                         return true;
642                 }
643         }
644 }