| 1 | /******************************************************************************* |
|---|---|
| 2 | * Copyright (c) 2004, 2022 IBM Corporation and others. |
| 3 | * |
| 4 | * This program and the accompanying materials |
| 5 | * are made available under the terms of the Eclipse Public License 2.0 |
| 6 | * which accompanies this distribution, and is available at |
| 7 | * https://www.eclipse.org/legal/epl-2.0/ |
| 8 | * |
| 9 | * SPDX-License-Identifier: EPL-2.0 |
| 10 | * |
| 11 | * Contributors: |
| 12 | * IBM Corporation - initial API and implementation |
| 13 | *******************************************************************************/ |
| 14 | package org.eclipse.jdt.core.dom; |
| 15 | |
| 16 | import java.util.ArrayList; |
| 17 | import java.util.Iterator; |
| 18 | import java.util.List; |
| 19 | import java.util.Map; |
| 20 | |
| 21 | import org.eclipse.jdt.core.compiler.CharOperation; |
| 22 | import org.eclipse.jdt.core.compiler.InvalidInputException; |
| 23 | import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| 24 | import org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser; |
| 25 | import org.eclipse.jdt.internal.compiler.parser.Scanner; |
| 26 | import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; |
| 27 | import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; |
| 28 | |
| 29 | /** |
| 30 | * Internal parser used for decoding doc comments. |
| 31 | * |
| 32 | * @since 3.0 |
| 33 | */ |
| 34 | @SuppressWarnings({ "rawtypes", "unchecked" }) |
| 35 | class DocCommentParser extends AbstractCommentParser { |
| 36 | |
| 37 | private Javadoc docComment; |
| 38 | private AST ast; |
| 39 | |
| 40 | DocCommentParser(AST ast, Scanner scanner, boolean check) { |
| 41 | super(null); |
| 42 | this.ast = ast; |
| 43 | this.scanner = scanner; |
| 44 | switch(this.ast.apiLevel()) { |
| 45 | case AST.JLS2_INTERNAL : |
| 46 | this.sourceLevel = ClassFileConstants.JDK1_3; |
| 47 | break; |
| 48 | case AST.JLS3_INTERNAL: |
| 49 | this.sourceLevel = ClassFileConstants.JDK1_5; |
| 50 | break; |
| 51 | default: |
| 52 | // AST.JLS4 for now |
| 53 | this.sourceLevel = ClassFileConstants.JDK1_7; |
| 54 | } |
| 55 | this.checkDocComment = check; |
| 56 | this.kind = DOM_PARSER | TEXT_PARSE; |
| 57 | } |
| 58 | |
| 59 | /* (non-Javadoc) |
| 60 | * Returns true if tag @deprecated is present in annotation. |
| 61 | * |
| 62 | * If annotation checking is enabled, will also construct an Annotation node, which will be stored into Parser.annotation |
| 63 | * slot for being consumed later on. |
| 64 | */ |
| 65 | public Javadoc parse(int[] positions) { |
| 66 | return parse(positions[0], positions[1]-positions[0]); |
| 67 | } |
| 68 | public Javadoc parse(int start, int length) { |
| 69 | |
| 70 | // Init |
| 71 | this.source = this.scanner.source; |
| 72 | this.lineEnds = this.scanner.lineEnds; |
| 73 | this.docComment = new Javadoc(this.ast); |
| 74 | |
| 75 | // Parse |
| 76 | if (this.checkDocComment) { |
| 77 | this.javadocStart = start; |
| 78 | this.javadocEnd = start+length-1; |
| 79 | this.firstTagPosition = this.javadocStart; |
| 80 | commentParse(); |
| 81 | } |
| 82 | this.docComment.setSourceRange(start, length); |
| 83 | if (this.ast.apiLevel == AST.JLS2_INTERNAL) { |
| 84 | setComment(start, length); // backward compatibility |
| 85 | } |
| 86 | return this.docComment; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Sets the comment starting at the given position and with the given length. |
| 91 | * <p> |
| 92 | * Note the only purpose of this method is to hide deprecated warnings. |
| 93 | * @deprecated mark deprecated to hide deprecated usage |
| 94 | */ |
| 95 | private void setComment(int start, int length) { |
| 96 | this.docComment.setComment(new String(this.source, start, length)); |
| 97 | } |
| 98 | |
| 99 | @Override |
| 100 | public String toString() { |
| 101 | StringBuilder buffer = new StringBuilder(); |
| 102 | buffer.append("javadoc: ").append(this.docComment).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| 103 | buffer.append(super.toString()); |
| 104 | return buffer.toString(); |
| 105 | } |
| 106 | |
| 107 | @Override |
| 108 | protected Object createArgumentReference(char[] name, int dim, boolean isVarargs, Object typeRef, long[] dimPositions, long argNamePos) throws InvalidInputException { |
| 109 | try { |
| 110 | MethodRefParameter argument = this.ast.newMethodRefParameter(); |
| 111 | ASTNode node = (ASTNode) typeRef; |
| 112 | int argStart = node.getStartPosition(); |
| 113 | int argEnd = node.getStartPosition()+node.getLength()-1; |
| 114 | if (dim > 0) argEnd = (int) dimPositions[dim-1]; |
| 115 | if (argNamePos >= 0) argEnd = (int) argNamePos; |
| 116 | if (name.length != 0) { |
| 117 | final SimpleName argName = new SimpleName(this.ast); |
| 118 | argName.internalSetIdentifier(new String(name)); |
| 119 | argument.setName(argName); |
| 120 | int argNameStart = (int) (argNamePos >>> 32); |
| 121 | argName.setSourceRange(argNameStart, argEnd-argNameStart+1); |
| 122 | } |
| 123 | Type argType = null; |
| 124 | if (node.getNodeType() == ASTNode.PRIMITIVE_TYPE) { |
| 125 | argType = (PrimitiveType) node; |
| 126 | } else { |
| 127 | Name argTypeName = (Name) node; |
| 128 | argType = this.ast.newSimpleType(argTypeName); |
| 129 | argType.setSourceRange(argStart, node.getLength()); |
| 130 | } |
| 131 | if (dim > 0 && !isVarargs) { |
| 132 | if (this.ast.apiLevel <= AST.JLS4_INTERNAL) { |
| 133 | for (int i=0; i<dim; i++) { |
| 134 | argType = this.ast.newArrayType(argType); |
| 135 | argType.setSourceRange(argStart, ((int) dimPositions[i])-argStart+1); |
| 136 | } |
| 137 | } else { |
| 138 | ArrayType argArrayType = this.ast.newArrayType(argType, 0); |
| 139 | argType = argArrayType; |
| 140 | argType.setSourceRange(argStart, ((int) dimPositions[dim-1])-argStart+1); |
| 141 | for (int i=0; i<dim; i++) { |
| 142 | Dimension dimension = this.ast.newDimension(); |
| 143 | int dimStart = (int) (dimPositions[i] >>> 32); |
| 144 | int dimEnd = (int) dimPositions[i]; |
| 145 | dimension.setSourceRange(dimStart, dimEnd-dimStart+1); |
| 146 | argArrayType.dimensions().add(dimension); |
| 147 | } |
| 148 | } |
| 149 | } |
| 150 | argument.setType(argType); |
| 151 | if (this.ast.apiLevel > AST.JLS8_INTERNAL) { |
| 152 | argument.setVarargs(isVarargs); |
| 153 | } |
| 154 | argument.setSourceRange(argStart, argEnd - argStart + 1); |
| 155 | return argument; |
| 156 | } |
| 157 | catch (ClassCastException ex) { |
| 158 | throw new InvalidInputException(); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | @Override |
| 163 | protected Object createFieldReference(Object receiver) throws InvalidInputException { |
| 164 | try { |
| 165 | MemberRef fieldRef = this.ast.newMemberRef(); |
| 166 | SimpleName fieldName = new SimpleName(this.ast); |
| 167 | fieldName.internalSetIdentifier(new String(this.identifierStack[0])); |
| 168 | fieldRef.setName(fieldName); |
| 169 | int start = (int) (this.identifierPositionStack[0] >>> 32); |
| 170 | int end = (int) this.identifierPositionStack[0]; |
| 171 | fieldName.setSourceRange(start, end - start + 1); |
| 172 | if (receiver == null) { |
| 173 | start = this.memberStart; |
| 174 | fieldRef.setSourceRange(start, end - start + 1); |
| 175 | } else { |
| 176 | Name typeRef = (Name) receiver; |
| 177 | fieldRef.setQualifier(typeRef); |
| 178 | start = typeRef.getStartPosition(); |
| 179 | end = fieldName.getStartPosition()+fieldName.getLength()-1; |
| 180 | fieldRef.setSourceRange(start, end-start+1); |
| 181 | } |
| 182 | return fieldRef; |
| 183 | } |
| 184 | catch (ClassCastException ex) { |
| 185 | throw new InvalidInputException(); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | @Override |
| 190 | protected Object createMethodReference(Object receiver, List arguments) throws InvalidInputException { |
| 191 | try { |
| 192 | // Create method ref |
| 193 | MethodRef methodRef = this.ast.newMethodRef(); |
| 194 | SimpleName methodName = new SimpleName(this.ast); |
| 195 | int length = this.identifierLengthStack[0] - 1; // may be > 0 for member class constructor reference |
| 196 | methodName.internalSetIdentifier(new String(this.identifierStack[length])); |
| 197 | methodRef.setName(methodName); |
| 198 | int start = (int) (this.identifierPositionStack[length] >>> 32); |
| 199 | int end = (int) this.identifierPositionStack[length]; |
| 200 | methodName.setSourceRange(start, end - start + 1); |
| 201 | // Set qualifier |
| 202 | if (receiver == null) { |
| 203 | start = this.memberStart; |
| 204 | methodRef.setSourceRange(start, end - start + 1); |
| 205 | } else { |
| 206 | Name typeRef = (Name) receiver; |
| 207 | methodRef.setQualifier(typeRef); |
| 208 | start = typeRef.getStartPosition(); |
| 209 | } |
| 210 | // Add arguments |
| 211 | if (arguments != null) { |
| 212 | Iterator parameters = arguments.listIterator(); |
| 213 | while (parameters.hasNext()) { |
| 214 | MethodRefParameter param = (MethodRefParameter) parameters.next(); |
| 215 | methodRef.parameters().add(param); |
| 216 | } |
| 217 | } |
| 218 | methodRef.setSourceRange(start, this.scanner.getCurrentTokenEndPosition()-start+1); |
| 219 | return methodRef; |
| 220 | } |
| 221 | catch (ClassCastException ex) { |
| 222 | throw new InvalidInputException(); |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | @Override |
| 227 | protected void createTag() { |
| 228 | int position = this.scanner.currentPosition; |
| 229 | this.scanner.resetTo(this.tagSourceStart, this.tagSourceEnd); |
| 230 | StringBuilder tagName = new StringBuilder(); |
| 231 | int start = this.tagSourceStart; |
| 232 | this.scanner.getNextChar(); |
| 233 | while (this.scanner.currentPosition <= (this.tagSourceEnd+1)) { |
| 234 | tagName.append(this.scanner.currentCharacter); |
| 235 | this.scanner.getNextChar(); |
| 236 | } |
| 237 | if (TagElement.TAG_SNIPPET.equals(tagName.toString())) { |
| 238 | this.tagSourceEnd = this.index; |
| 239 | // need to use createSnippetTag to create @snippet |
| 240 | return; |
| 241 | } |
| 242 | TagElement tagElement = this.ast.newTagElement(); |
| 243 | tagElement.setTagName(tagName.toString()); |
| 244 | if (this.inlineTagStarted) { |
| 245 | start = this.inlineTagStart; |
| 246 | TagElement previousTag = null; |
| 247 | if (this.astPtr == -1) { |
| 248 | previousTag = this.ast.newTagElement(); |
| 249 | previousTag.setSourceRange(start, this.tagSourceEnd-start+1); |
| 250 | pushOnAstStack(previousTag, true); |
| 251 | } else { |
| 252 | previousTag = (TagElement) this.astStack[this.astPtr]; |
| 253 | } |
| 254 | int previousStart = previousTag.getStartPosition(); |
| 255 | previousTag.fragments().add(tagElement); |
| 256 | previousTag.setSourceRange(previousStart, this.tagSourceEnd-previousStart+1); |
| 257 | } else { |
| 258 | pushOnAstStack(tagElement, true); |
| 259 | } |
| 260 | tagElement.setSourceRange(start, this.tagSourceEnd-start+1); |
| 261 | this.scanner.resetTo(position, this.javadocEnd); |
| 262 | } |
| 263 | |
| 264 | @Override |
| 265 | protected Object createSnippetTag() { |
| 266 | TagElement tagElement = this.ast.newTagElement(); |
| 267 | int position = this.scanner.currentPosition; |
| 268 | this.scanner.resetTo(this.tagSourceStart, this.tagSourceEnd); |
| 269 | StringBuilder tagName = new StringBuilder(); |
| 270 | int start = this.tagSourceStart; |
| 271 | this.scanner.getNextChar(); |
| 272 | while (this.scanner.currentPosition <= (this.tagSourceEnd+1)) { |
| 273 | tagName.append(this.scanner.currentCharacter); |
| 274 | this.scanner.getNextChar(); |
| 275 | } |
| 276 | tagElement.setTagName(tagName.toString()); |
| 277 | if (this.inlineTagStarted) { |
| 278 | start = this.inlineTagStart; |
| 279 | TagElement previousTag = null; |
| 280 | if (this.astPtr == -1) { |
| 281 | previousTag = this.ast.newTagElement(); |
| 282 | previousTag.setSourceRange(start, this.tagSourceEnd-start+1); |
| 283 | pushOnAstStack(previousTag, true); |
| 284 | } else { |
| 285 | previousTag = (TagElement) this.astStack[this.astPtr]; |
| 286 | } |
| 287 | int previousStart = previousTag.getStartPosition(); |
| 288 | previousTag.fragments().add(tagElement); |
| 289 | previousTag.setSourceRange(previousStart, this.tagSourceEnd-previousStart+1); |
| 290 | } else { |
| 291 | pushOnAstStack(tagElement, true); |
| 292 | } |
| 293 | tagElement.setSourceRange(start, this.tagSourceEnd-start+1); |
| 294 | this.scanner.resetTo(position, this.javadocEnd); |
| 295 | return tagElement; |
| 296 | } |
| 297 | |
| 298 | @Override |
| 299 | protected void setSnippetIsValid(Object tag, boolean value) { |
| 300 | if (tag instanceof TagElement) { |
| 301 | ((TagElement) tag).setProperty(TagProperty.TAG_PROPERTY_SNIPPET_IS_VALID, value); |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | @Override |
| 306 | protected void setSnippetError(Object tag, String value) { |
| 307 | if (tag instanceof TagElement) { |
| 308 | ((TagElement) tag).setProperty(TagProperty.TAG_PROPERTY_SNIPPET_ERROR, value); |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | @Override |
| 313 | protected void setSnippetID(Object tag, String value) { |
| 314 | if (tag instanceof TagElement) { |
| 315 | ((TagElement) tag).setProperty(TagProperty.TAG_PROPERTY_SNIPPET_ID, value); |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | @Override |
| 320 | protected Object createSnippetRegion(String name, List<Object> tags, Object snippetTag, boolean isDummyRegion, boolean considerPrevTag) { |
| 321 | if (!isDummyRegion) { |
| 322 | return createSnippetOriginalRegion(name, tags); |
| 323 | } |
| 324 | List<TagElement> tagsToBeProcessed = new ArrayList<>(); |
| 325 | Object toBeReturned = null; |
| 326 | if (tags != null && tags.size() > 0) { |
| 327 | int start = -1; |
| 328 | int end = -1; |
| 329 | for (Object tag : tags) { |
| 330 | if (tag instanceof TagElement) { |
| 331 | TagElement tagElem = (TagElement) tag; |
| 332 | tagsToBeProcessed.add(tagElem); |
| 333 | int tagStart = tagElem.getStartPosition(); |
| 334 | int tagEnd = tagStart + tagElem.getLength(); |
| 335 | if (start == -1 || start > tagStart) { |
| 336 | start = tagStart; |
| 337 | } |
| 338 | if (end ==-1 || end < tagEnd) { |
| 339 | end = tagEnd; |
| 340 | } |
| 341 | } else if (tag instanceof JavaDocRegion && snippetTag instanceof TagElement) { |
| 342 | TagElement snippet = (TagElement) snippetTag; |
| 343 | JavaDocRegion reg = (JavaDocRegion) tag; |
| 344 | if (!reg.isDummyRegion()) { |
| 345 | snippet.fragments().add(reg); |
| 346 | } |
| 347 | toBeReturned = reg; |
| 348 | } |
| 349 | } |
| 350 | if (tagsToBeProcessed.size() > 0) { |
| 351 | boolean process = true; |
| 352 | if (considerPrevTag && snippetTag instanceof TagElement) { |
| 353 | TagElement snippetTagElem = (TagElement) snippetTag; |
| 354 | Object prevTag = snippetTagElem.fragments().get(snippetTagElem.fragments().size() - 1); |
| 355 | if (prevTag instanceof TagElement) { |
| 356 | tagsToBeProcessed.add(0, (TagElement)prevTag); |
| 357 | snippetTagElem.fragments().remove(prevTag); |
| 358 | } else if (prevTag instanceof JavaDocRegion) { |
| 359 | JavaDocRegion region = (JavaDocRegion) prevTag; |
| 360 | region.tags().addAll(tagsToBeProcessed); |
| 361 | toBeReturned = snippetTag; |
| 362 | process = false; |
| 363 | } |
| 364 | } |
| 365 | if (process) { |
| 366 | if (tagsToBeProcessed.size() == 1) { |
| 367 | return tagsToBeProcessed.get(0); |
| 368 | } |
| 369 | else { |
| 370 | JavaDocRegion region = this.ast.newJavaDocRegion(); |
| 371 | region.tags().addAll(tagsToBeProcessed); |
| 372 | region.setSourceRange(start, end); |
| 373 | toBeReturned = region; |
| 374 | } |
| 375 | } |
| 376 | } else { |
| 377 | toBeReturned = snippetTag; |
| 378 | } |
| 379 | } |
| 380 | return toBeReturned; |
| 381 | } |
| 382 | |
| 383 | private Object createSnippetOriginalRegion(String name, List<Object> tags) { |
| 384 | JavaDocRegion region = this.ast.newJavaDocRegion(); |
| 385 | if (tags != null && tags.size() > 0) { |
| 386 | int start = -1; |
| 387 | int end = -1; |
| 388 | for (Object tag : tags) { |
| 389 | if (tag instanceof TagElement) { |
| 390 | TagElement tagElem = (TagElement) tag; |
| 391 | region.tags().add(tagElem); |
| 392 | int tagStart = tagElem.getStartPosition(); |
| 393 | int tagEnd = tagStart + tagElem.getLength(); |
| 394 | if (start == -1 || start > tagStart) { |
| 395 | start = tagStart; |
| 396 | } |
| 397 | if (end ==-1 || end < tagEnd) { |
| 398 | end = tagEnd; |
| 399 | } |
| 400 | } |
| 401 | } |
| 402 | region.setSourceRange(start, end-start); |
| 403 | region.setDummyRegion(false); |
| 404 | } |
| 405 | if (name != null) { |
| 406 | region.setTagName(name); |
| 407 | } |
| 408 | return region; |
| 409 | } |
| 410 | |
| 411 | @Override |
| 412 | protected Object createSnippetInnerTag(String tagName, int start, int end) { |
| 413 | if (tagName != null) { |
| 414 | TagElement tagElement = this.ast.newTagElement(); |
| 415 | tagElement.setTagName(tagName.toString()); |
| 416 | if (this.astPtr == -1) { |
| 417 | return null; |
| 418 | } |
| 419 | tagElement.setSourceRange(start, end-start); |
| 420 | return tagElement; |
| 421 | } |
| 422 | return null; |
| 423 | } |
| 424 | |
| 425 | @Override |
| 426 | protected void closeJavaDocRegion(String name, Object snippetTag, int end) { |
| 427 | if (snippetTag instanceof TagElement) { |
| 428 | TagElement snippet = (TagElement) snippetTag; |
| 429 | List<JavaDocRegion> regions = snippet.tagRegions(); |
| 430 | JavaDocRegion regionToClose = null; |
| 431 | if (name != null) { |
| 432 | for (JavaDocRegion region : regions) { |
| 433 | if (name.equals(region.getTagName())) { |
| 434 | if (!this.isRegionToBeEnded(region) |
| 435 | && !this.hasRegionEnded(region)) { |
| 436 | regionToClose = region; |
| 437 | break; |
| 438 | } |
| 439 | } |
| 440 | } |
| 441 | } else { |
| 442 | for (int i= regions.size()-1; i >-1; i--) { |
| 443 | JavaDocRegion region = regions.get(i); |
| 444 | if (!this.isRegionToBeEnded(region) |
| 445 | && !this.hasRegionEnded(region)) { |
| 446 | regionToClose = region; |
| 447 | break; |
| 448 | } |
| 449 | } |
| 450 | } |
| 451 | if (regionToClose != null) { |
| 452 | setRegionToBeEnded(regionToClose, true); |
| 453 | int start = regionToClose.getStartPosition(); |
| 454 | int curEnd = start + regionToClose.getLength(); |
| 455 | if (end > curEnd) { |
| 456 | regionToClose.setSourceRange(start, end-start); |
| 457 | } |
| 458 | } |
| 459 | } |
| 460 | |
| 461 | } |
| 462 | |
| 463 | @Override |
| 464 | protected void addTagProperties(Object tag, Map<String, Object> map, int tagCount) { |
| 465 | if (tag instanceof TagElement) { |
| 466 | TagElement tagElement = (TagElement) tag; |
| 467 | map.forEach((k, v) -> { |
| 468 | TagProperty tagProperty = this.ast.newTagProperty(); |
| 469 | tagProperty.setName(k); |
| 470 | if (v instanceof String) { |
| 471 | tagProperty.setStringValue((String)v); |
| 472 | } else if (v instanceof ASTNode) { |
| 473 | tagProperty.setNodeValue((ASTNode)v); |
| 474 | } |
| 475 | tagElement.tagProperties().add(tagProperty); |
| 476 | }); |
| 477 | tagElement.setProperty(TagProperty.TAG_PROPERTY_SNIPPET_INLINE_TAG_COUNT, tagCount); |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | @Override |
| 482 | protected void addSnippetInnerTag(Object obj, Object snippetTag) { |
| 483 | boolean isNotDummyRegion = false; |
| 484 | if (obj instanceof JavaDocRegion) { |
| 485 | JavaDocRegion region = (JavaDocRegion) obj; |
| 486 | if (snippetTag instanceof TagElement) { |
| 487 | TagElement snippetTagElem = (TagElement) snippetTag; |
| 488 | if (!region.isDummyRegion()) { |
| 489 | snippetTagElem.fragments().add(region); |
| 490 | isNotDummyRegion = true; |
| 491 | } else { |
| 492 | Iterator<Object> itr = region.tags().iterator(); |
| 493 | while (itr.hasNext()) { |
| 494 | Object tag = itr.next(); |
| 495 | if (tag instanceof JavaDocRegion) { |
| 496 | JavaDocRegion reg = (JavaDocRegion) tag; |
| 497 | if (!reg.isDummyRegion()) { |
| 498 | region.tags().remove(reg); |
| 499 | snippetTagElem.fragments().add(reg); |
| 500 | } |
| 501 | } |
| 502 | } |
| 503 | } |
| 504 | } |
| 505 | } |
| 506 | if (obj instanceof AbstractTagElement && !isNotDummyRegion) { |
| 507 | AbstractTagElement tagElement = (AbstractTagElement) obj; |
| 508 | AbstractTagElement previousTag = null; |
| 509 | if (this.astPtr == -1) { |
| 510 | return; |
| 511 | } else { |
| 512 | previousTag = (AbstractTagElement) this.astStack[this.astPtr]; |
| 513 | List fragments = previousTag.fragments(); |
| 514 | if (this.inlineTagStarted) { |
| 515 | int size = fragments.size(); |
| 516 | if (size == 0) { |
| 517 | //do nothing |
| 518 | } else { |
| 519 | // If last fragment is a tag, then use it as previous tag |
| 520 | ASTNode lastFragment = (ASTNode) fragments.get(size-1); |
| 521 | if (lastFragment instanceof AbstractTagElement) { |
| 522 | previousTag = (AbstractTagElement) lastFragment; |
| 523 | } |
| 524 | } |
| 525 | } |
| 526 | } |
| 527 | previousTag.fragments().add(tagElement); |
| 528 | } |
| 529 | } |
| 530 | |
| 531 | |
| 532 | @Override |
| 533 | protected Object createTypeReference(int primitiveToken, boolean canBeModule) { |
| 534 | return createTypeReference(primitiveToken); |
| 535 | } |
| 536 | |
| 537 | @Override |
| 538 | protected Object createTypeReference(int primitiveToken) { |
| 539 | int size = this.identifierLengthStack[this.identifierLengthPtr]; |
| 540 | String[] identifiers = new String[size]; |
| 541 | int pos = this.identifierPtr - size + 1; |
| 542 | for (int i = 0; i < size; i++) { |
| 543 | identifiers[i] = new String(this.identifierStack[pos+i]); |
| 544 | } |
| 545 | ASTNode typeRef = null; |
| 546 | if (primitiveToken == -1) { |
| 547 | typeRef = this.ast.internalNewName(identifiers); |
| 548 | } else { |
| 549 | switch (primitiveToken) { |
| 550 | case TerminalTokens.TokenNamevoid : |
| 551 | typeRef = this.ast.newPrimitiveType(PrimitiveType.VOID); |
| 552 | break; |
| 553 | case TerminalTokens.TokenNameboolean : |
| 554 | typeRef = this.ast.newPrimitiveType(PrimitiveType.BOOLEAN); |
| 555 | break; |
| 556 | case TerminalTokens.TokenNamebyte : |
| 557 | typeRef = this.ast.newPrimitiveType(PrimitiveType.BYTE); |
| 558 | break; |
| 559 | case TerminalTokens.TokenNamechar : |
| 560 | typeRef = this.ast.newPrimitiveType(PrimitiveType.CHAR); |
| 561 | break; |
| 562 | case TerminalTokens.TokenNamedouble : |
| 563 | typeRef = this.ast.newPrimitiveType(PrimitiveType.DOUBLE); |
| 564 | break; |
| 565 | case TerminalTokens.TokenNamefloat : |
| 566 | typeRef = this.ast.newPrimitiveType(PrimitiveType.FLOAT); |
| 567 | break; |
| 568 | case TerminalTokens.TokenNameint : |
| 569 | typeRef = this.ast.newPrimitiveType(PrimitiveType.INT); |
| 570 | break; |
| 571 | case TerminalTokens.TokenNamelong : |
| 572 | typeRef = this.ast.newPrimitiveType(PrimitiveType.LONG); |
| 573 | break; |
| 574 | case TerminalTokens.TokenNameshort : |
| 575 | typeRef = this.ast.newPrimitiveType(PrimitiveType.SHORT); |
| 576 | break; |
| 577 | default: |
| 578 | // should not happen |
| 579 | return null; |
| 580 | } |
| 581 | } |
| 582 | // Update ref for whole name |
| 583 | int start = (int) (this.identifierPositionStack[pos] >>> 32); |
| 584 | // int end = (int) this.identifierPositionStack[this.identifierPtr]; |
| 585 | // typeRef.setSourceRange(start, end-start+1); |
| 586 | // Update references of each simple name |
| 587 | if (size > 1) { |
| 588 | Name name = (Name)typeRef; |
| 589 | int nameIndex = size; |
| 590 | for (int i=this.identifierPtr; i>pos; i--, nameIndex--) { |
| 591 | int s = (int) (this.identifierPositionStack[i] >>> 32); |
| 592 | int e = (int) this.identifierPositionStack[i]; |
| 593 | name.index = nameIndex; |
| 594 | SimpleName simpleName = ((QualifiedName)name).getName(); |
| 595 | simpleName.index = nameIndex; |
| 596 | simpleName.setSourceRange(s, e-s+1); |
| 597 | name.setSourceRange(start, e-start+1); |
| 598 | name = ((QualifiedName)name).getQualifier(); |
| 599 | } |
| 600 | int end = (int) this.identifierPositionStack[pos]; |
| 601 | name.setSourceRange(start, end-start+1); |
| 602 | name.index = nameIndex; |
| 603 | } else { |
| 604 | int end = (int) this.identifierPositionStack[pos]; |
| 605 | typeRef.setSourceRange(start, end-start+1); |
| 606 | } |
| 607 | return typeRef; |
| 608 | } |
| 609 | |
| 610 | private ModuleQualifiedName createModuleReference(int moduleRefTokenCount) { |
| 611 | String[] identifiers = new String[moduleRefTokenCount]; |
| 612 | for (int i = 0; i < moduleRefTokenCount; i++) { |
| 613 | identifiers[i] = new String(this.identifierStack[i]); |
| 614 | } |
| 615 | ModuleQualifiedName moduleRef = new ModuleQualifiedName(this.ast); |
| 616 | |
| 617 | ASTNode typeRef = null; |
| 618 | typeRef = this.ast.internalNewName(identifiers); |
| 619 | int start = (int) (this.identifierPositionStack[0] >>> 32); |
| 620 | if (moduleRefTokenCount > 1) { |
| 621 | Name name = (Name)typeRef; |
| 622 | int nameIndex = moduleRefTokenCount; |
| 623 | for (int i=moduleRefTokenCount-1; i>0; i--, nameIndex--) { |
| 624 | int s = (int) (this.identifierPositionStack[i] >>> 32); |
| 625 | int e = (int) this.identifierPositionStack[i]; |
| 626 | name.index = nameIndex; |
| 627 | SimpleName simpleName = ((QualifiedName)name).getName(); |
| 628 | simpleName.index = nameIndex; |
| 629 | simpleName.setSourceRange(s, e-s+1); |
| 630 | name.setSourceRange(start, e-start+1); |
| 631 | name = ((QualifiedName)name).getQualifier(); |
| 632 | } |
| 633 | int end = (int) this.identifierPositionStack[0]; |
| 634 | name.setSourceRange(start, end-start+1); |
| 635 | name.index = nameIndex; |
| 636 | } else { |
| 637 | int end = (int) this.identifierPositionStack[0]; |
| 638 | typeRef.setSourceRange(start, end-start+1); |
| 639 | } |
| 640 | moduleRef.setModuleQualifier((Name)typeRef); |
| 641 | moduleRef.setName(null); |
| 642 | moduleRef.setSourceRange(typeRef.getStartPosition(), typeRef.getLength()+1); |
| 643 | return moduleRef; |
| 644 | } |
| 645 | |
| 646 | @Override |
| 647 | protected Object createModuleTypeReference(int primitiveToken, int moduleRefTokenCount) { |
| 648 | int size = this.identifierLengthStack[this.identifierLengthPtr]; |
| 649 | ModuleQualifiedName moduleRef= null; |
| 650 | Name typeRef= null; |
| 651 | if (size == moduleRefTokenCount) { |
| 652 | moduleRef= createModuleReference(moduleRefTokenCount); |
| 653 | this.lastIdentifierEndPosition++; |
| 654 | } else { |
| 655 | String[] moduleIdentifiers = new String[moduleRefTokenCount]; |
| 656 | String[] identifiers = new String[size- moduleRefTokenCount]; |
| 657 | int pos = this.identifierPtr - size + 1; |
| 658 | for (int i = 0; i < size; i++) { |
| 659 | if (i < moduleRefTokenCount) { |
| 660 | moduleIdentifiers[i] = new String(this.identifierStack[pos+i]); |
| 661 | } else { |
| 662 | identifiers[i-moduleRefTokenCount] = new String(this.identifierStack[pos+i]); |
| 663 | } |
| 664 | } |
| 665 | moduleRef= createModuleReference(moduleRefTokenCount); |
| 666 | pos = this.identifierPtr+moduleRefTokenCount - size + 1; |
| 667 | |
| 668 | if (primitiveToken == -1) { |
| 669 | typeRef = this.ast.internalNewName(identifiers); |
| 670 | // Update ref for whole name |
| 671 | int start = (int) (this.identifierPositionStack[pos] >>> 32); |
| 672 | // int end = (int) this.identifierPositionStack[this.identifierPtr]; |
| 673 | // typeRef.setSourceRange(start, end-start+1); |
| 674 | // Update references of each simple name |
| 675 | if (size-moduleRefTokenCount > 1) { |
| 676 | Name name = typeRef; |
| 677 | int nameIndex = size-moduleRefTokenCount; |
| 678 | for (int i=this.identifierPtr; i>pos; i--, nameIndex--) { |
| 679 | int s = (int) (this.identifierPositionStack[i] >>> 32); |
| 680 | int e = (int) this.identifierPositionStack[i]; |
| 681 | name.index = nameIndex; |
| 682 | SimpleName simpleName = ((QualifiedName)name).getName(); |
| 683 | simpleName.index = nameIndex; |
| 684 | simpleName.setSourceRange(s, e-s+1); |
| 685 | name.setSourceRange(start, e-start+1); |
| 686 | name = ((QualifiedName)name).getQualifier(); |
| 687 | } |
| 688 | int end = (int) this.identifierPositionStack[pos]; |
| 689 | name.setSourceRange(start, end-start+1); |
| 690 | name.index = nameIndex; |
| 691 | } else { |
| 692 | int end = (int) this.identifierPositionStack[pos]; |
| 693 | typeRef.setSourceRange(start, end-start+1); |
| 694 | } |
| 695 | moduleRef.setName(typeRef); |
| 696 | moduleRef.setSourceRange(moduleRef.getStartPosition(), moduleRef.getLength() + typeRef.getLength()); |
| 697 | } |
| 698 | } |
| 699 | return moduleRef; |
| 700 | } |
| 701 | |
| 702 | @Override |
| 703 | protected boolean parseIdentifierTag(boolean report) { |
| 704 | if (super.parseIdentifierTag(report)) { |
| 705 | createTag(); |
| 706 | this.index = this.tagSourceEnd+1; |
| 707 | this.scanner.resetTo(this.index, this.javadocEnd); |
| 708 | return true; |
| 709 | } |
| 710 | return false; |
| 711 | } |
| 712 | |
| 713 | /* |
| 714 | * Parse @return tag declaration |
| 715 | */ |
| 716 | protected boolean parseReturn() { |
| 717 | createTag(); |
| 718 | return true; |
| 719 | } |
| 720 | |
| 721 | @Override |
| 722 | protected boolean parseTag(int previousPosition) throws InvalidInputException { |
| 723 | |
| 724 | // Read tag name |
| 725 | int currentPosition = this.index; |
| 726 | int token = readTokenAndConsume(); |
| 727 | char[] tagName = CharOperation.NO_CHAR; |
| 728 | if (currentPosition == this.scanner.startPosition) { |
| 729 | this.tagSourceStart = this.scanner.getCurrentTokenStartPosition(); |
| 730 | this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition(); |
| 731 | tagName = this.scanner.getCurrentIdentifierSource(); |
| 732 | } else { |
| 733 | this.tagSourceEnd = currentPosition-1; |
| 734 | } |
| 735 | |
| 736 | // Try to get tag name other than java identifier |
| 737 | // (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660) |
| 738 | if (this.scanner.currentCharacter != ' ' && !ScannerHelper.isWhitespace(this.scanner.currentCharacter)) { |
| 739 | tagNameToken: while (token != TerminalTokens.TokenNameEOF && this.index < this.scanner.eofPosition) { |
| 740 | int length = tagName.length; |
| 741 | // !, ", #, %, &, ', -, :, <, >, * chars and spaces are not allowed in tag names |
| 742 | switch (this.scanner.currentCharacter) { |
| 743 | case '}': |
| 744 | case '*': // break for '*' as this is perhaps the end of comment (bug 65288) |
| 745 | case '!': |
| 746 | case '#': |
| 747 | case '%': |
| 748 | case '&': |
| 749 | case '\'': |
| 750 | case '"': |
| 751 | case ':': |
| 752 | case '<': |
| 753 | case '>': |
| 754 | break tagNameToken; |
| 755 | case '-': // allowed in tag names as this character is often used in doclets (bug 68087) |
| 756 | System.arraycopy(tagName, 0, tagName = new char[length+1], 0, length); |
| 757 | tagName[length] = this.scanner.currentCharacter; |
| 758 | break; |
| 759 | default: |
| 760 | if (this.scanner.currentCharacter == ' ' || ScannerHelper.isWhitespace(this.scanner.currentCharacter)) { |
| 761 | break tagNameToken; |
| 762 | } |
| 763 | token = readTokenAndConsume(); |
| 764 | char[] ident = this.scanner.getCurrentIdentifierSource(); |
| 765 | System.arraycopy(tagName, 0, tagName = new char[length+ident.length], 0, length); |
| 766 | System.arraycopy(ident, 0, tagName, length, ident.length); |
| 767 | break; |
| 768 | } |
| 769 | this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition(); |
| 770 | this.scanner.getNextChar(); |
| 771 | this.index = this.scanner.currentPosition; |
| 772 | } |
| 773 | } |
| 774 | int length = tagName.length; |
| 775 | this.index = this.tagSourceEnd+1; |
| 776 | this.scanner.currentPosition = this.tagSourceEnd+1; |
| 777 | this.tagSourceStart = previousPosition; |
| 778 | |
| 779 | // tage name may be empty (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=125903) |
| 780 | if (tagName.length == 0) { |
| 781 | return false; |
| 782 | } |
| 783 | |
| 784 | // Decide which parse to perform depending on tag name |
| 785 | this.tagValue = NO_TAG_VALUE; |
| 786 | boolean valid = true; |
| 787 | switch (token) { |
| 788 | case TerminalTokens.TokenNameIdentifier : |
| 789 | switch (tagName[0]) { |
| 790 | case 'c': |
| 791 | if (length == TAG_CATEGORY_LENGTH && CharOperation.equals(TAG_CATEGORY, tagName)) { |
| 792 | this.tagValue = TAG_CATEGORY_VALUE; |
| 793 | valid = parseIdentifierTag(false); // TODO (frederic) reconsider parameter value when @category will be significant in spec |
| 794 | } else if (length == TAG_CODE_LENGTH && CharOperation.equals(TAG_CODE, tagName)) { |
| 795 | this.tagValue = TAG_CODE_VALUE; |
| 796 | createTag(); |
| 797 | } else { |
| 798 | this.tagValue = TAG_OTHERS_VALUE; |
| 799 | createTag(); |
| 800 | } |
| 801 | break; |
| 802 | case 'd': |
| 803 | if (length == TAG_DEPRECATED_LENGTH && CharOperation.equals(TAG_DEPRECATED, tagName)) { |
| 804 | this.deprecated = true; |
| 805 | this.tagValue = TAG_DEPRECATED_VALUE; |
| 806 | } else { |
| 807 | this.tagValue = TAG_OTHERS_VALUE; |
| 808 | } |
| 809 | createTag(); |
| 810 | break; |
| 811 | case 'i': |
| 812 | if (length == TAG_INHERITDOC_LENGTH && CharOperation.equals(TAG_INHERITDOC, tagName)) { |
| 813 | if (this.reportProblems) { |
| 814 | recordInheritedPosition((((long) this.tagSourceStart) << 32) + this.tagSourceEnd); |
| 815 | } |
| 816 | this.tagValue = TAG_INHERITDOC_VALUE; |
| 817 | } else { |
| 818 | this.tagValue = TAG_OTHERS_VALUE; |
| 819 | } |
| 820 | createTag(); |
| 821 | break; |
| 822 | case 'p': |
| 823 | if (length == TAG_PARAM_LENGTH && CharOperation.equals(TAG_PARAM, tagName)) { |
| 824 | this.tagValue = TAG_PARAM_VALUE; |
| 825 | valid = parseParam(); |
| 826 | } else { |
| 827 | this.tagValue = TAG_OTHERS_VALUE; |
| 828 | createTag(); |
| 829 | } |
| 830 | break; |
| 831 | case 'e': |
| 832 | if (length == TAG_EXCEPTION_LENGTH && CharOperation.equals(TAG_EXCEPTION, tagName)) { |
| 833 | this.tagValue = TAG_EXCEPTION_VALUE; |
| 834 | valid = parseThrows(); |
| 835 | } else { |
| 836 | this.tagValue = TAG_OTHERS_VALUE; |
| 837 | createTag(); |
| 838 | } |
| 839 | break; |
| 840 | case 's': |
| 841 | if (length == TAG_SEE_LENGTH && CharOperation.equals(TAG_SEE, tagName)) { |
| 842 | this.tagValue = TAG_SEE_VALUE; |
| 843 | if (this.inlineTagStarted) { |
| 844 | // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290 |
| 845 | // Cannot have @see inside inline comment |
| 846 | valid = false; |
| 847 | } else { |
| 848 | valid = parseReference(true); |
| 849 | } |
| 850 | } else if (length == TAG_SNIPPET_LENGTH && CharOperation.equals(TAG_SNIPPET, tagName)) { |
| 851 | this.tagValue = TAG_SNIPPET_LENGTH; |
| 852 | if (!this.inlineTagStarted) { |
| 853 | // @snippet is an inline comment |
| 854 | valid = false; |
| 855 | } else { |
| 856 | this.tagValue = TAG_SNIPPET_VALUE; |
| 857 | valid = parseSnippet(); |
| 858 | } |
| 859 | } else { |
| 860 | this.tagValue = TAG_OTHERS_VALUE; |
| 861 | createTag(); |
| 862 | } |
| 863 | break; |
| 864 | case 'l': |
| 865 | if (length == TAG_LINK_LENGTH && CharOperation.equals(TAG_LINK, tagName)) { |
| 866 | this.tagValue = TAG_LINK_VALUE; |
| 867 | } else if (length == TAG_LINKPLAIN_LENGTH && CharOperation.equals(TAG_LINKPLAIN, tagName)) { |
| 868 | this.tagValue = TAG_LINKPLAIN_VALUE; |
| 869 | } else if (length == TAG_LITERAL_LENGTH && CharOperation.equals(TAG_LITERAL, tagName)) { |
| 870 | this.tagValue = TAG_LITERAL_VALUE; |
| 871 | } |
| 872 | |
| 873 | if (this.tagValue != NO_TAG_VALUE && this.tagValue != TAG_LITERAL_VALUE) { |
| 874 | if (this.inlineTagStarted) { |
| 875 | valid = parseReference(true); |
| 876 | } else { |
| 877 | // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290 |
| 878 | // Cannot have @link outside inline comment |
| 879 | valid = false; |
| 880 | } |
| 881 | } else { |
| 882 | if (this.tagValue == NO_TAG_VALUE) this.tagValue = TAG_OTHERS_VALUE; |
| 883 | createTag(); |
| 884 | } |
| 885 | break; |
| 886 | case 'v': |
| 887 | if (this.sourceLevel >= ClassFileConstants.JDK1_5 && length == TAG_VALUE_LENGTH && CharOperation.equals(TAG_VALUE, tagName)) { |
| 888 | this.tagValue = TAG_VALUE_VALUE; |
| 889 | if (this.inlineTagStarted) { |
| 890 | valid = parseReference(); |
| 891 | } else { |
| 892 | valid = false; |
| 893 | } |
| 894 | } else { |
| 895 | this.tagValue = TAG_OTHERS_VALUE; |
| 896 | createTag(); |
| 897 | } |
| 898 | break; |
| 899 | default: |
| 900 | this.tagValue = TAG_OTHERS_VALUE; |
| 901 | createTag(); |
| 902 | } |
| 903 | break; |
| 904 | case TerminalTokens.TokenNamereturn : |
| 905 | this.tagValue = TAG_RETURN_VALUE; |
| 906 | valid = parseReturn(); |
| 907 | break; |
| 908 | case TerminalTokens.TokenNamethrows : |
| 909 | this.tagValue = TAG_THROWS_VALUE; |
| 910 | valid = parseThrows(); |
| 911 | break; |
| 912 | case TerminalTokens.TokenNameabstract: |
| 913 | case TerminalTokens.TokenNameassert: |
| 914 | case TerminalTokens.TokenNameboolean: |
| 915 | case TerminalTokens.TokenNamebreak: |
| 916 | case TerminalTokens.TokenNamebyte: |
| 917 | case TerminalTokens.TokenNamecase: |
| 918 | case TerminalTokens.TokenNamecatch: |
| 919 | case TerminalTokens.TokenNamechar: |
| 920 | case TerminalTokens.TokenNameclass: |
| 921 | case TerminalTokens.TokenNamecontinue: |
| 922 | case TerminalTokens.TokenNamedefault: |
| 923 | case TerminalTokens.TokenNamedo: |
| 924 | case TerminalTokens.TokenNamedouble: |
| 925 | case TerminalTokens.TokenNameelse: |
| 926 | case TerminalTokens.TokenNameextends: |
| 927 | case TerminalTokens.TokenNamefalse: |
| 928 | case TerminalTokens.TokenNamefinal: |
| 929 | case TerminalTokens.TokenNamefinally: |
| 930 | case TerminalTokens.TokenNamefloat: |
| 931 | case TerminalTokens.TokenNamefor: |
| 932 | case TerminalTokens.TokenNameif: |
| 933 | case TerminalTokens.TokenNameimplements: |
| 934 | case TerminalTokens.TokenNameimport: |
| 935 | case TerminalTokens.TokenNameinstanceof: |
| 936 | case TerminalTokens.TokenNameint: |
| 937 | case TerminalTokens.TokenNameinterface: |
| 938 | case TerminalTokens.TokenNamelong: |
| 939 | case TerminalTokens.TokenNamenative: |
| 940 | case TerminalTokens.TokenNamenew: |
| 941 | case TerminalTokens.TokenNamenull: |
| 942 | case TerminalTokens.TokenNamepackage: |
| 943 | case TerminalTokens.TokenNameprivate: |
| 944 | case TerminalTokens.TokenNameprotected: |
| 945 | case TerminalTokens.TokenNamepublic: |
| 946 | case TerminalTokens.TokenNameshort: |
| 947 | case TerminalTokens.TokenNamestatic: |
| 948 | case TerminalTokens.TokenNamestrictfp: |
| 949 | case TerminalTokens.TokenNamesuper: |
| 950 | case TerminalTokens.TokenNameswitch: |
| 951 | case TerminalTokens.TokenNamesynchronized: |
| 952 | case TerminalTokens.TokenNamethis: |
| 953 | case TerminalTokens.TokenNamethrow: |
| 954 | case TerminalTokens.TokenNametransient: |
| 955 | case TerminalTokens.TokenNametrue: |
| 956 | case TerminalTokens.TokenNametry: |
| 957 | case TerminalTokens.TokenNamevoid: |
| 958 | case TerminalTokens.TokenNamevolatile: |
| 959 | case TerminalTokens.TokenNamewhile: |
| 960 | case TerminalTokens.TokenNameenum : |
| 961 | case TerminalTokens.TokenNameconst : |
| 962 | case TerminalTokens.TokenNamegoto : |
| 963 | this.tagValue = TAG_OTHERS_VALUE; |
| 964 | createTag(); |
| 965 | break; |
| 966 | } |
| 967 | this.textStart = this.index; |
| 968 | return valid; |
| 969 | } |
| 970 | |
| 971 | @Override |
| 972 | protected boolean pushParamName(boolean isTypeParam) { |
| 973 | int idIndex = isTypeParam ? 1 : 0; |
| 974 | final SimpleName name = new SimpleName(this.ast); |
| 975 | name.internalSetIdentifier(new String(this.identifierStack[idIndex])); |
| 976 | int nameStart = (int) (this.identifierPositionStack[idIndex] >>> 32); |
| 977 | int nameEnd = (int) (this.identifierPositionStack[idIndex] & 0x00000000FFFFFFFFL); |
| 978 | name.setSourceRange(nameStart, nameEnd-nameStart+1); |
| 979 | TagElement paramTag = this.ast.newTagElement(); |
| 980 | paramTag.setTagName(TagElement.TAG_PARAM); |
| 981 | if (isTypeParam) { // specific storage for @param <E> (see bug 79809) |
| 982 | // '<' was stored in identifiers stack |
| 983 | TextElement text = this.ast.newTextElement(); |
| 984 | text.setText(new String(this.identifierStack[0])); |
| 985 | int txtStart = (int) (this.identifierPositionStack[0] >>> 32); |
| 986 | int txtEnd = (int) (this.identifierPositionStack[0] & 0x00000000FFFFFFFFL); |
| 987 | text.setSourceRange(txtStart, txtEnd-txtStart+1); |
| 988 | paramTag.fragments().add(text); |
| 989 | // add simple name |
| 990 | paramTag.fragments().add(name); |
| 991 | // '>' was stored in identifiers stack |
| 992 | text = this.ast.newTextElement(); |
| 993 | text.setText(new String(this.identifierStack[2])); |
| 994 | txtStart = (int) (this.identifierPositionStack[2] >>> 32); |
| 995 | txtEnd = (int) (this.identifierPositionStack[2] & 0x00000000FFFFFFFFL); |
| 996 | text.setSourceRange(txtStart, txtEnd-txtStart+1); |
| 997 | paramTag.fragments().add(text); |
| 998 | // set param tag source range |
| 999 | paramTag.setSourceRange(this.tagSourceStart, txtEnd-this.tagSourceStart+1); |
| 1000 | } else { |
| 1001 | paramTag.setSourceRange(this.tagSourceStart, nameEnd-this.tagSourceStart+1); |
| 1002 | paramTag.fragments().add(name); |
| 1003 | } |
| 1004 | pushOnAstStack(paramTag, true); |
| 1005 | return true; |
| 1006 | } |
| 1007 | |
| 1008 | @Override |
| 1009 | protected boolean pushSeeRef(Object statement) { |
| 1010 | TagElement seeTag = this.ast.newTagElement(); |
| 1011 | ASTNode node = (ASTNode) statement; |
| 1012 | seeTag.fragments().add(node); |
| 1013 | int end = node.getStartPosition()+node.getLength()-1; |
| 1014 | if (this.inlineTagStarted) { |
| 1015 | seeTag.setSourceRange(this.inlineTagStart, end-this.inlineTagStart+1); |
| 1016 | switch (this.tagValue) { |
| 1017 | case TAG_LINK_VALUE: |
| 1018 | seeTag.setTagName(TagElement.TAG_LINK); |
| 1019 | break; |
| 1020 | case TAG_LINKPLAIN_VALUE: |
| 1021 | seeTag.setTagName(TagElement.TAG_LINKPLAIN); |
| 1022 | break; |
| 1023 | case TAG_VALUE_VALUE: |
| 1024 | seeTag.setTagName(TagElement.TAG_VALUE); |
| 1025 | break; |
| 1026 | } |
| 1027 | TagElement previousTag = null; |
| 1028 | int previousStart = this.inlineTagStart; |
| 1029 | if (this.astPtr == -1) { |
| 1030 | previousTag = this.ast.newTagElement(); |
| 1031 | pushOnAstStack(previousTag, true); |
| 1032 | } else { |
| 1033 | previousTag = (TagElement) this.astStack[this.astPtr]; |
| 1034 | previousStart = previousTag.getStartPosition(); |
| 1035 | } |
| 1036 | previousTag.fragments().add(seeTag); |
| 1037 | previousTag.setSourceRange(previousStart, end-previousStart+1); |
| 1038 | } else { |
| 1039 | seeTag.setTagName(TagElement.TAG_SEE); |
| 1040 | seeTag.setSourceRange(this.tagSourceStart, end-this.tagSourceStart+1); |
| 1041 | pushOnAstStack(seeTag, true); |
| 1042 | } |
| 1043 | return true; |
| 1044 | } |
| 1045 | |
| 1046 | @Override |
| 1047 | protected void pushText(int start, int end) { |
| 1048 | |
| 1049 | // Create text element |
| 1050 | TextElement text = this.ast.newTextElement(); |
| 1051 | text.setText(new String( this.source, start, end-start)); |
| 1052 | text.setSourceRange(start, end-start); |
| 1053 | |
| 1054 | // Search previous tag on which to add the text element |
| 1055 | TagElement previousTag = null; |
| 1056 | int previousStart = start; |
| 1057 | if (this.astPtr == -1) { |
| 1058 | previousTag = this.ast.newTagElement(); |
| 1059 | previousTag.setSourceRange(start, end-start); |
| 1060 | pushOnAstStack(previousTag, true); |
| 1061 | } else { |
| 1062 | previousTag = (TagElement) this.astStack[this.astPtr]; |
| 1063 | previousStart = previousTag.getStartPosition(); |
| 1064 | } |
| 1065 | |
| 1066 | // If we're in a inline tag, then retrieve previous tag in its fragments |
| 1067 | List fragments = previousTag.fragments(); |
| 1068 | if (this.inlineTagStarted) { |
| 1069 | int size = fragments.size(); |
| 1070 | if (size == 0) { |
| 1071 | // no existing fragment => just add the element |
| 1072 | TagElement inlineTag = this.ast.newTagElement(); |
| 1073 | fragments.add(inlineTag); |
| 1074 | previousTag = inlineTag; |
| 1075 | } else { |
| 1076 | // If last fragment is a tag, then use it as previous tag |
| 1077 | ASTNode lastFragment = (ASTNode) fragments.get(size-1); |
| 1078 | if (lastFragment.getNodeType() == ASTNode.TAG_ELEMENT) { |
| 1079 | previousTag = (TagElement) lastFragment; |
| 1080 | previousStart = previousTag.getStartPosition(); |
| 1081 | } |
| 1082 | } |
| 1083 | } |
| 1084 | |
| 1085 | // Add the text |
| 1086 | previousTag.fragments().add(text); |
| 1087 | previousTag.setSourceRange(previousStart, end-previousStart); |
| 1088 | this.textStart = -1; |
| 1089 | } |
| 1090 | |
| 1091 | @Override |
| 1092 | protected void pushSnippetText(int start, int end, boolean addNewLine, Object snippetTag) { |
| 1093 | |
| 1094 | // Create text element |
| 1095 | TextElement text = this.ast.newTextElement(); |
| 1096 | String textToBeAdded= new String( this.source, start, end-start); |
| 1097 | int iindex = textToBeAdded.indexOf('*'); |
| 1098 | if (iindex > -1 && textToBeAdded.substring(0, iindex+1).trim().equals("*")) { //$NON-NLS-1$ |
| 1099 | textToBeAdded = textToBeAdded.substring(iindex+1); |
| 1100 | if (addNewLine) { |
| 1101 | textToBeAdded += System.lineSeparator(); |
| 1102 | } |
| 1103 | } |
| 1104 | text.setText(textToBeAdded); |
| 1105 | text.setSourceRange(start, end-start); |
| 1106 | |
| 1107 | // Search previous tag on which to add the text element |
| 1108 | AbstractTagElement previousTag = null; |
| 1109 | int previousStart = start; |
| 1110 | if (this.astPtr == -1) { |
| 1111 | previousTag = this.ast.newTagElement(); |
| 1112 | previousTag.setSourceRange(start, end-start); |
| 1113 | pushOnAstStack(previousTag, true); |
| 1114 | } else { |
| 1115 | previousTag = (AbstractTagElement) this.astStack[this.astPtr]; |
| 1116 | previousStart = previousTag.getStartPosition(); |
| 1117 | } |
| 1118 | |
| 1119 | AbstractTagElement prevTag = null; |
| 1120 | // If we're in a inline tag, then retrieve previous tag in its fragments |
| 1121 | List fragments = previousTag.fragments(); |
| 1122 | if (this.inlineTagStarted) { |
| 1123 | int size = fragments.size(); |
| 1124 | if (size == 0) { |
| 1125 | //do nothing |
| 1126 | } else { |
| 1127 | // If last fragment is a tag, then use it as previous tag |
| 1128 | ASTNode lastFragment = (ASTNode) fragments.get(size-1); |
| 1129 | if (lastFragment instanceof AbstractTagElement) { |
| 1130 | previousTag = (AbstractTagElement) lastFragment; |
| 1131 | previousStart = previousTag.getStartPosition(); |
| 1132 | if (this.snippetInlineTagStarted) { |
| 1133 | fragments = previousTag.fragments(); |
| 1134 | size = fragments.size(); |
| 1135 | if (size == 0) { |
| 1136 | //do nothing |
| 1137 | } else { |
| 1138 | lastFragment = (ASTNode) fragments.get(size-1); |
| 1139 | if (lastFragment instanceof AbstractTagElement) { |
| 1140 | prevTag = (AbstractTagElement) lastFragment; |
| 1141 | this.snippetInlineTagStarted = false; |
| 1142 | } |
| 1143 | } |
| 1144 | this.snippetInlineTagStarted = false; |
| 1145 | } |
| 1146 | } |
| 1147 | } |
| 1148 | } |
| 1149 | |
| 1150 | int finEnd = end; |
| 1151 | boolean isNotDummyJavaDocRegion = false; |
| 1152 | if (prevTag instanceof JavaDocRegion && !((JavaDocRegion)prevTag).isDummyRegion()) { |
| 1153 | isNotDummyJavaDocRegion = true; |
| 1154 | } |
| 1155 | // Add the text |
| 1156 | if (prevTag != null && !isNotDummyJavaDocRegion) { |
| 1157 | |
| 1158 | prevTag.fragments().add(text); |
| 1159 | int curStart = prevTag.getStartPosition(); |
| 1160 | int curEnd = curStart + prevTag.getLength(); |
| 1161 | int finStart = start; |
| 1162 | if (curStart < start) { |
| 1163 | finStart = curStart; |
| 1164 | } |
| 1165 | if (curEnd > end) { |
| 1166 | finEnd = curEnd; |
| 1167 | } |
| 1168 | prevTag.setSourceRange(finStart, finEnd - finStart); |
| 1169 | } else { |
| 1170 | previousTag.fragments().add(text); |
| 1171 | } |
| 1172 | previousTag.setSourceRange(previousStart, finEnd-previousStart); |
| 1173 | this.textStart = -1; |
| 1174 | |
| 1175 | if (snippetTag instanceof TagElement) { |
| 1176 | fragments = ((TagElement) snippetTag).fragments(); |
| 1177 | for (Object frag : fragments) { |
| 1178 | if (frag instanceof JavaDocRegion) { |
| 1179 | JavaDocRegion region = (JavaDocRegion) frag; |
| 1180 | if (!region.isDummyRegion() && !hasRegionEnded(region)) { |
| 1181 | int startPos = region.getStartPosition(); |
| 1182 | int endPos = startPos + region.getLength(); |
| 1183 | if (startPos > start) { |
| 1184 | startPos = start; |
| 1185 | } |
| 1186 | if (endPos < end) { |
| 1187 | endPos = end; |
| 1188 | } |
| 1189 | Object textVal = region.getProperty(TagProperty.TAG_PROPERTY_SNIPPET_REGION_TEXT); |
| 1190 | if (!(textVal instanceof TextElement)) { |
| 1191 | region.setProperty(TagProperty.TAG_PROPERTY_SNIPPET_REGION_TEXT, text); |
| 1192 | } |
| 1193 | region.setSourceRange(startPos, endPos-startPos); |
| 1194 | if (isRegionToBeEnded(region)) { |
| 1195 | setRegionEnded(region, true); |
| 1196 | } |
| 1197 | } |
| 1198 | } |
| 1199 | } |
| 1200 | } |
| 1201 | } |
| 1202 | |
| 1203 | private boolean hasRegionEnded(JavaDocRegion region) { |
| 1204 | boolean ended = false; |
| 1205 | if (region != null) { |
| 1206 | Object value = region.getProperty(JavaDocRegion.REGION_ENDED); |
| 1207 | if (value instanceof Boolean && ((Boolean)value).booleanValue()) { |
| 1208 | ended = true; |
| 1209 | } |
| 1210 | } |
| 1211 | return ended; |
| 1212 | } |
| 1213 | |
| 1214 | private boolean isRegionToBeEnded(JavaDocRegion region) { |
| 1215 | boolean toBeEnded = false; |
| 1216 | if (region != null) { |
| 1217 | Object value = region.getProperty(JavaDocRegion.REGION_TO_BE_ENDED); |
| 1218 | if (value instanceof Boolean && ((Boolean)value).booleanValue()) { |
| 1219 | toBeEnded = true; |
| 1220 | } |
| 1221 | } |
| 1222 | return toBeEnded; |
| 1223 | } |
| 1224 | |
| 1225 | private void setRegionToBeEnded(JavaDocRegion region, boolean value) { |
| 1226 | if (region != null) { |
| 1227 | region.setProperty(JavaDocRegion.REGION_TO_BE_ENDED, value); |
| 1228 | } |
| 1229 | } |
| 1230 | |
| 1231 | private void setRegionEnded(JavaDocRegion region, boolean value) { |
| 1232 | if (region != null) { |
| 1233 | region.setProperty(JavaDocRegion.REGION_ENDED, value); |
| 1234 | setRegionToBeEnded(region, !value); |
| 1235 | } |
| 1236 | } |
| 1237 | |
| 1238 | @Override |
| 1239 | protected void pushExternalSnippetText(String text,int start, int end) { |
| 1240 | String snippetLangHeader = "<pre>"; //$NON-NLS-1$ //the code snippets comes as preformatted so need to prefix them with <pre> tag |
| 1241 | String snipperLangFooter = "</pre>"; //$NON-NLS-1$ |
| 1242 | text = snippetLangHeader + text + snipperLangFooter; |
| 1243 | TextElement textElement = this.ast.newTextElement(); |
| 1244 | textElement.setText(text); |
| 1245 | textElement.setSourceRange(start, end-start); |
| 1246 | |
| 1247 | // Search previous tag on which to add the text element |
| 1248 | TagElement previousTag = null; |
| 1249 | int previousStart = start; |
| 1250 | int previousEnd = end; |
| 1251 | if (this.astPtr == -1) { |
| 1252 | previousTag = this.ast.newTagElement(); |
| 1253 | previousTag.setSourceRange(start, end-start); |
| 1254 | pushOnAstStack(previousTag, true); |
| 1255 | } else { |
| 1256 | previousTag = (TagElement) this.astStack[this.astPtr]; |
| 1257 | previousStart = previousTag.getStartPosition(); |
| 1258 | previousEnd = previousStart + previousTag.getLength(); |
| 1259 | } |
| 1260 | previousTag.fragments().add(textElement); |
| 1261 | int curStart = previousStart; |
| 1262 | int curEnd = previousEnd; |
| 1263 | if (start < previousStart) { |
| 1264 | curStart = start; |
| 1265 | } |
| 1266 | if (end > previousEnd) { |
| 1267 | curEnd = end; |
| 1268 | } |
| 1269 | previousTag.setSourceRange(curStart, curEnd-curStart); |
| 1270 | this.textStart = -1; |
| 1271 | } |
| 1272 | |
| 1273 | |
| 1274 | @Override |
| 1275 | protected boolean pushThrowName(Object typeRef) { |
| 1276 | TagElement throwsTag = this.ast.newTagElement(); |
| 1277 | switch (this.tagValue) { |
| 1278 | case TAG_THROWS_VALUE: |
| 1279 | throwsTag.setTagName(TagElement.TAG_THROWS); |
| 1280 | break; |
| 1281 | case TAG_EXCEPTION_VALUE: |
| 1282 | throwsTag.setTagName(TagElement.TAG_EXCEPTION); |
| 1283 | break; |
| 1284 | } |
| 1285 | throwsTag.setSourceRange(this.tagSourceStart, this.scanner.getCurrentTokenEndPosition()-this.tagSourceStart+1); |
| 1286 | throwsTag.fragments().add(typeRef); |
| 1287 | pushOnAstStack(throwsTag, true); |
| 1288 | return true; |
| 1289 | } |
| 1290 | |
| 1291 | @Override |
| 1292 | protected void refreshInlineTagPosition(int previousPosition) { |
| 1293 | if (this.astPtr != -1) { |
| 1294 | TagElement previousTag = (TagElement) this.astStack[this.astPtr]; |
| 1295 | if (this.inlineTagStarted) { |
| 1296 | int previousStart = previousTag.getStartPosition(); |
| 1297 | previousTag.setSourceRange(previousStart, previousPosition-previousStart+1); |
| 1298 | if (previousTag.fragments().size() > 0) { |
| 1299 | ASTNode inlineTag = (ASTNode) previousTag.fragments().get(previousTag.fragments().size()-1); |
| 1300 | if (inlineTag.getNodeType() == ASTNode.TAG_ELEMENT) { |
| 1301 | int inlineStart = inlineTag.getStartPosition(); |
| 1302 | inlineTag.setSourceRange(inlineStart, previousPosition-inlineStart+1); |
| 1303 | } |
| 1304 | } |
| 1305 | } |
| 1306 | } |
| 1307 | } |
| 1308 | |
| 1309 | /* |
| 1310 | * Add stored tag elements to associated comment. |
| 1311 | */ |
| 1312 | @Override |
| 1313 | protected void updateDocComment() { |
| 1314 | for (int idx = 0; idx <= this.astPtr; idx++) { |
| 1315 | this.docComment.tags().add(this.astStack[idx]); |
| 1316 | } |
| 1317 | } |
| 1318 | |
| 1319 | @Override |
| 1320 | protected boolean areRegionsClosed() { |
| 1321 | // do nothing |
| 1322 | return true; |
| 1323 | } |
| 1324 | |
| 1325 | @Override |
| 1326 | protected void setRegionPosition(int currentPosition) { |
| 1327 | // do nothing |
| 1328 | } |
| 1329 | } |
| 1330 |
Members