| /******************************************************************************* |
| * Copyright (c) 2000, 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.parser; |
| |
| import java.util.List; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.core.compiler.InvalidInputException; |
| import org.eclipse.jdt.internal.compiler.ast.Expression; |
| import org.eclipse.jdt.internal.compiler.ast.ImplicitDocTypeReference; |
| import org.eclipse.jdt.internal.compiler.ast.Javadoc; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocAllocationExpression; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocArgumentExpression; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocArrayQualifiedTypeReference; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocArraySingleTypeReference; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocFieldReference; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocMessageSend; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocQualifiedTypeReference; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocReturnStatement; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocSingleNameReference; |
| import org.eclipse.jdt.internal.compiler.ast.JavadocSingleTypeReference; |
| import org.eclipse.jdt.internal.compiler.ast.TypeReference; |
| import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; |
| import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; |
| |
| /** |
| * Parser specialized for decoding javadoc comments |
| */ |
| public class JavadocParser extends AbstractCommentParser { |
| |
| // Public fields |
| public Javadoc docComment; |
| |
| // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600 |
| // Store param references for tag with invalid syntax |
| private int invParamsPtr = -1; |
| private JavadocSingleNameReference[] invParamsStack; |
| |
| JavadocParser(Parser sourceParser) { |
| super(sourceParser); |
| this.checkDocComment = this.sourceParser.options.docCommentSupport; |
| this.kind = COMPIL_PARSER; |
| } |
| |
| /* (non-Javadoc) |
| * Returns true if tag @deprecated is present in javadoc comment. |
| * |
| * If javadoc checking is enabled, will also construct an Javadoc node, which will be stored into Parser.javadoc |
| * slot for being consumed later on. |
| */ |
| public boolean checkDeprecation(int javadocStart, int javadocEnd) { |
| |
| try { |
| this.source = this.sourceParser.scanner.source; |
| this.lineEnds = this.sourceParser.scanner.getLineEnds(); |
| this.index = javadocStart +3; |
| this.endComment = javadocEnd - 2; |
| if (this.checkDocComment) { |
| // Initialization |
| this.docComment = new Javadoc(javadocStart, javadocEnd); |
| parseComment(javadocStart, javadocEnd); |
| } else { |
| // Init javadoc if necessary |
| if (this.sourceParser.options.getSeverity(CompilerOptions.MissingJavadocComments) != ProblemSeverities.Ignore) { |
| this.docComment = new Javadoc(javadocStart, javadocEnd); |
| } else { |
| this.docComment = null; |
| } |
| |
| // Parse comment |
| int firstLineNumber = this.sourceParser.scanner.getLineNumber(javadocStart); |
| int lastLineNumber = this.sourceParser.scanner.getLineNumber(javadocEnd); |
| |
| // scan line per line, since tags must be at beginning of lines only |
| nextLine : for (int line = firstLineNumber; line <= lastLineNumber; line++) { |
| int lineStart = line == firstLineNumber |
| ? javadocStart + 3 // skip leading /** |
| : this.sourceParser.scanner.getLineStart(line); |
| this.index = lineStart; |
| this.lineEnd = line == lastLineNumber |
| ? javadocEnd - 2 // remove trailing * / |
| : this.sourceParser.scanner.getLineEnd(line); |
| nextCharacter : while (this.index < this.lineEnd) { |
| char c = readChar(); // consider unicodes |
| switch (c) { |
| default : |
| if (Character.isWhitespace(c)) { |
| continue nextCharacter; |
| } |
| break; |
| case '*' : |
| continue nextCharacter; |
| case '@' : |
| if ((readChar() == 'd') && (readChar() == 'e') && |
| (readChar() == 'p') && (readChar() == 'r') && |
| (readChar() == 'e') && (readChar() == 'c') && |
| (readChar() == 'a') && (readChar() == 't') && |
| (readChar() == 'e') && (readChar() == 'd')) { |
| // ensure the tag is properly ended: either followed by a space, a tab, line end or asterisk. |
| c = readChar(); |
| if (Character.isWhitespace(c) || c == '*') { |
| return true; |
| } |
| } |
| } |
| continue nextLine; |
| } |
| } |
| return false; |
| } |
| } finally { |
| this.source = null; // release source as soon as finished |
| } |
| return this.deprecated; |
| } |
| |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("check javadoc: ").append(this.checkDocComment).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| buffer.append("javadoc: ").append(this.docComment).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| buffer.append(super.toString()); |
| return buffer.toString(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createArgumentReference(char[], java.lang.Object, int) |
| */ |
| protected Object createArgumentReference(char[] name, int dim, Object typeRef, long[] dimPositions, long argNamePos) throws InvalidInputException { |
| try { |
| TypeReference argTypeRef = (TypeReference) typeRef; |
| if (dim > 0) { |
| long pos = (((long) argTypeRef.sourceStart) << 32) + argTypeRef.sourceEnd; |
| if (typeRef instanceof JavadocSingleTypeReference) { |
| JavadocSingleTypeReference singleRef = (JavadocSingleTypeReference) typeRef; |
| argTypeRef = new JavadocArraySingleTypeReference(singleRef.token, dim, pos); |
| } else { |
| JavadocQualifiedTypeReference qualifRef = (JavadocQualifiedTypeReference) typeRef; |
| argTypeRef = new JavadocArrayQualifiedTypeReference(qualifRef, dim); |
| } |
| } |
| int argEnd = argTypeRef.sourceEnd; |
| if (dim > 0) argEnd = (int) dimPositions[dim-1]; |
| if (argNamePos >= 0) argEnd = (int) argNamePos; |
| return new JavadocArgumentExpression(name, argTypeRef.sourceStart, argEnd, argTypeRef); |
| } |
| catch (ClassCastException ex) { |
| throw new InvalidInputException(); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createFieldReference() |
| */ |
| protected Object createFieldReference(Object receiver) throws InvalidInputException { |
| try { |
| // Get receiver type |
| TypeReference typeRef = (TypeReference) receiver; |
| if (typeRef == null) { |
| char[] name = this.sourceParser.compilationUnit.compilationResult.compilationUnit.getMainTypeName(); |
| typeRef = new ImplicitDocTypeReference(name, this.memberStart); |
| } |
| // Create field |
| JavadocFieldReference field = new JavadocFieldReference(this.identifierStack[0], this.identifierPositionStack[0]); |
| field.receiver = typeRef; |
| field.tagSourceStart = this.tagSourceStart; |
| field.tagSourceEnd = this.tagSourceEnd; |
| return field; |
| } |
| catch (ClassCastException ex) { |
| throw new InvalidInputException(); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createMethodReference(java.lang.Object[]) |
| */ |
| protected Object createMethodReference(Object receiver, List arguments) throws InvalidInputException { |
| try { |
| // Get receiver type |
| TypeReference typeRef = (TypeReference) receiver; |
| // Decide whether we have a constructor or not |
| boolean isConstructor = false; |
| if (typeRef == null) { |
| char[] name = this.sourceParser.compilationUnit.compilationResult.compilationUnit.getMainTypeName(); |
| isConstructor = CharOperation.equals(this.identifierStack[0], name); |
| typeRef = new ImplicitDocTypeReference(name, this.memberStart); |
| } else { |
| char[] name = null; |
| if (typeRef instanceof JavadocSingleTypeReference) { |
| name = ((JavadocSingleTypeReference)typeRef).token; |
| } else if (typeRef instanceof JavadocQualifiedTypeReference) { |
| char[][] tokens = ((JavadocQualifiedTypeReference)typeRef).tokens; |
| name = tokens[tokens.length-1]; |
| } else { |
| throw new InvalidInputException(); |
| } |
| isConstructor = CharOperation.equals(this.identifierStack[0], name); |
| } |
| // Create node |
| if (arguments == null) { |
| if (isConstructor) { |
| JavadocAllocationExpression expr = new JavadocAllocationExpression(this.identifierPositionStack[0]); |
| expr.type = typeRef; |
| return expr; |
| } else { |
| JavadocMessageSend msg = new JavadocMessageSend(this.identifierStack[0], this.identifierPositionStack[0]); |
| msg.receiver = typeRef; |
| return msg; |
| } |
| } else { |
| JavadocArgumentExpression[] expressions = new JavadocArgumentExpression[arguments.size()]; |
| arguments.toArray(expressions); |
| if (isConstructor) { |
| JavadocAllocationExpression alloc = new JavadocAllocationExpression(this.identifierPositionStack[0]); |
| alloc.arguments = expressions; |
| alloc.type = typeRef; |
| return alloc; |
| } else { |
| JavadocMessageSend msg = new JavadocMessageSend(this.identifierStack[0], this.identifierPositionStack[0], expressions); |
| msg.receiver = typeRef; |
| return msg; |
| } |
| } |
| } |
| catch (ClassCastException ex) { |
| throw new InvalidInputException(); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createReturnStatement() |
| */ |
| protected Object createReturnStatement() { |
| return new JavadocReturnStatement(this.scanner.getCurrentTokenStartPosition(), |
| this.scanner.getCurrentTokenEndPosition(), |
| this.scanner.getRawTokenSourceEnd()); |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createTypeReference() |
| */ |
| protected Object createTypeReference(int primitiveToken) { |
| TypeReference typeRef = null; |
| int size = this.identifierLengthStack[this.identifierLengthPtr--]; |
| if (size == 1) { // Single Type ref |
| typeRef = new JavadocSingleTypeReference( |
| this.identifierStack[this.identifierPtr], |
| this.identifierPositionStack[this.identifierPtr], |
| this.tagSourceStart, |
| this.tagSourceEnd); |
| } else if (size > 1) { // Qualified Type ref |
| char[][] tokens = new char[size][]; |
| System.arraycopy(this.identifierStack, this.identifierPtr - size + 1, tokens, 0, size); |
| long[] positions = new long[size]; |
| System.arraycopy(this.identifierPositionStack, this.identifierPtr - size + 1, positions, 0, size); |
| typeRef = new JavadocQualifiedTypeReference(tokens, positions, this.tagSourceStart, this.tagSourceEnd); |
| } |
| this.identifierPtr -= size; |
| return typeRef; |
| } |
| |
| /* |
| * Parse @return tag declaration |
| */ |
| protected boolean parseReturn() { |
| if (this.returnStatement == null) { |
| this.returnStatement = createReturnStatement(); |
| return true; |
| } |
| if (this.sourceParser != null) this.sourceParser.problemReporter().javadocDuplicatedReturnTag( |
| this.scanner.getCurrentTokenStartPosition(), |
| this.scanner.getCurrentTokenEndPosition()); |
| return false; |
| } |
| |
| /* |
| * Parse @return tag declaration |
| */ |
| protected boolean parseTag() { |
| return true; |
| } |
| |
| /* |
| * Push a param name in ast node stack. |
| */ |
| protected boolean pushParamName() { |
| // Create name reference |
| JavadocSingleNameReference nameRef = new JavadocSingleNameReference(this.scanner.getCurrentIdentifierSource(), |
| this.scanner.getCurrentTokenStartPosition(), |
| this.scanner.getCurrentTokenEndPosition()); |
| nameRef.tagSourceStart = this.tagSourceStart; |
| nameRef.tagSourceEnd = this.tagSourceEnd; |
| // Push ref on stack |
| if (this.astLengthPtr == -1) { // First push |
| pushOnAstStack(nameRef, true); |
| } else { |
| // Verify that no @throws has been declared before |
| for (int i=THROWS_TAG_EXPECTED_ORDER; i<=this.astLengthPtr; i+=ORDERED_TAGS_NUMBER) { |
| if (this.astLengthStack[i] != 0) { |
| if (this.sourceParser != null) this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd); |
| // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600 |
| // store param references in specific array |
| if (this.invParamsPtr == -1l) { |
| this.invParamsStack = new JavadocSingleNameReference[10]; |
| } |
| int stackLength = this.invParamsStack.length; |
| if (++this.invParamsPtr >= stackLength) { |
| System.arraycopy( |
| this.invParamsStack, 0, |
| this.invParamsStack = new JavadocSingleNameReference[stackLength + AstStackIncrement], 0, |
| stackLength); |
| } |
| this.invParamsStack[this.invParamsPtr] = nameRef; |
| return false; |
| } |
| } |
| switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) { |
| case PARAM_TAG_EXPECTED_ORDER : |
| // previous push was a @param tag => push another param name |
| pushOnAstStack(nameRef, false); |
| break; |
| case SEE_TAG_EXPECTED_ORDER : |
| // previous push was a @see tag => push new param name |
| pushOnAstStack(nameRef, true); |
| break; |
| default: |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /* |
| * Push a reference statement in ast node stack. |
| */ |
| protected boolean pushSeeRef(Object statement, boolean plain) { |
| if (this.astLengthPtr == -1) { // First push |
| pushOnAstStack(null, true); |
| pushOnAstStack(null, true); |
| pushOnAstStack(statement, true); |
| } else { |
| switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) { |
| case PARAM_TAG_EXPECTED_ORDER : |
| // previous push was a @param tag => push empty @throws tag and new @see tag |
| pushOnAstStack(null, true); |
| pushOnAstStack(statement, true); |
| break; |
| case THROWS_TAG_EXPECTED_ORDER : |
| // previous push was a @throws tag => push new @see tag |
| pushOnAstStack(statement, true); |
| break; |
| case SEE_TAG_EXPECTED_ORDER : |
| // previous push was a @see tag => push another @see tag |
| pushOnAstStack(statement, false); |
| break; |
| default: |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#pushText(int, int) |
| */ |
| protected void pushText(int start, int end) { |
| // compiler does not matter of text |
| } |
| |
| /* |
| * Push a throws type ref in ast node stack. |
| */ |
| protected boolean pushThrowName(Object typeRef, boolean real) { |
| if (this.astLengthPtr == -1) { // First push |
| pushOnAstStack(null, true); |
| pushOnAstStack(typeRef, true); |
| } else { |
| switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) { |
| case PARAM_TAG_EXPECTED_ORDER : |
| // previous push was a @param tag => push new @throws tag |
| pushOnAstStack(typeRef, true); |
| break; |
| case THROWS_TAG_EXPECTED_ORDER : |
| // previous push was a @throws tag => push another @throws tag |
| pushOnAstStack(typeRef, false); |
| break; |
| case SEE_TAG_EXPECTED_ORDER : |
| // previous push was a @see tag => push empty @param and new @throws tags |
| pushOnAstStack(null, true); |
| pushOnAstStack(typeRef, true); |
| break; |
| default: |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /* |
| * Fill javadoc fields with information in ast nodes stack. |
| */ |
| protected void updateDocComment() { |
| |
| // Set inherited flag |
| this.docComment.inherited = this.inherited; |
| |
| // Set return node if present |
| if (this.returnStatement != null) { |
| this.docComment.returnStatement = (JavadocReturnStatement) this.returnStatement; |
| } |
| |
| // Copy array of invalid syntax param tags |
| if (this.invParamsPtr >= 0) { |
| this.docComment.invalidParameters = new JavadocSingleNameReference[this.invParamsPtr+1]; |
| System.arraycopy(this.invParamsStack, 0, this.docComment.invalidParameters, 0, this.invParamsPtr+1); |
| } |
| |
| // If no nodes stored return |
| if (this.astLengthPtr == -1) { |
| return; |
| } |
| |
| // Initialize arrays |
| int[] sizes = new int[ORDERED_TAGS_NUMBER]; |
| for (int i=0; i<=this.astLengthPtr; i++) { |
| sizes[i%ORDERED_TAGS_NUMBER] += this.astLengthStack[i]; |
| } |
| this.docComment.references = new Expression[sizes[SEE_TAG_EXPECTED_ORDER]]; |
| this.docComment.thrownExceptions = new TypeReference[sizes[THROWS_TAG_EXPECTED_ORDER]]; |
| this.docComment.parameters = new JavadocSingleNameReference[sizes[PARAM_TAG_EXPECTED_ORDER]]; |
| |
| // Store nodes in arrays |
| while (this.astLengthPtr >= 0) { |
| int ptr = this.astLengthPtr % ORDERED_TAGS_NUMBER; |
| // Starting with the stack top, so get references (eg. Expression) coming from @see declarations |
| if (ptr == SEE_TAG_EXPECTED_ORDER) { |
| int size = this.astLengthStack[this.astLengthPtr--]; |
| for (int i=0; i<size; i++) { |
| this.docComment.references[--sizes[ptr]] = (Expression) this.astStack[this.astPtr--]; |
| } |
| } |
| |
| // Then continuing with class names (eg. TypeReference) coming from @throw/@exception declarations |
| else if (ptr == THROWS_TAG_EXPECTED_ORDER) { |
| int size = this.astLengthStack[this.astLengthPtr--]; |
| for (int i=0; i<size; i++) { |
| this.docComment.thrownExceptions[--sizes[ptr]] = (TypeReference) this.astStack[this.astPtr--]; |
| } |
| } |
| |
| // Finally, finishing with parameters nales (ie. Argument) coming from @param declaration |
| else if (ptr == PARAM_TAG_EXPECTED_ORDER) { |
| int size = this.astLengthStack[this.astLengthPtr--]; |
| for (int i=0; i<size; i++) { |
| this.docComment.parameters[--sizes[ptr]] = (JavadocSingleNameReference) this.astStack[this.astPtr--]; |
| } |
| } |
| } |
| } |
| } |