--- /dev/null
+/*******************************************************************************
+ * 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.index = javadocStart +3;
+ this.endComment = javadocEnd - 2;
+ if (this.checkDocComment) {
+ // Initialization
+ this.scanner.lineEnds = this.sourceParser.scanner.lineEnds;
+ this.scanner.linePtr = this.sourceParser.scanner.linePtr;
+ this.lineEnds = this.scanner.lineEnds;
+ 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 associated comment fields with ast nodes information stored in 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--];
+ }
+ }
+ }
+ }
+}