/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Olivier Oeuillot <oeuillot@free.fr> - bug 244411 *******************************************************************************/ package org.eclipse.wst.jsdt.internal.compiler.parser; import java.util.List; import org.eclipse.wst.jsdt.core.compiler.CharOperation; import org.eclipse.wst.jsdt.core.compiler.InvalidInputException; import org.eclipse.wst.jsdt.internal.compiler.ast.ASTNode; import org.eclipse.wst.jsdt.internal.compiler.ast.Expression; import org.eclipse.wst.jsdt.internal.compiler.ast.Javadoc; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocAllocationExpression; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocArgumentExpression; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocArrayQualifiedTypeReference; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocArraySingleTypeReference; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocFieldReference; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocImplicitTypeReference; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocMessageSend; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocQualifiedTypeReference; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocReturnStatement; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocSingleNameReference; import org.eclipse.wst.jsdt.internal.compiler.ast.JavadocSingleTypeReference; import org.eclipse.wst.jsdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.wst.jsdt.internal.compiler.ast.TypeReference; import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.wst.jsdt.internal.compiler.util.Util; /** * 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 invalidParamReferencesPtr = -1; private ASTNode[] invalidParamReferencesStack; // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=153399 // Store value tag positions private long validValuePositions, invalidValuePositions; private int flags=0; private TypeReference namespace=null; private TypeReference memberOf=null; private TypeReference returnType=null; private TypeReference extendsType=null; private TypeReference classDef=null; private TypeReference methodDef=null; JavadocSingleNameReference property=null; private boolean isConstructor; protected boolean commentParse() { this.isConstructor=false; this.namespace=null; this.flags=0; this.namespace=null; this.memberOf=null; this.returnType=null; this.extendsType=null; this.property=null; this.classDef=null; this.methodDef=null; // TODO Auto-generated method stub boolean result= super.commentParse(); return result; } public JavadocParser(Parser sourceParser) { super(sourceParser); this.kind = COMPIL_PARSER | TEXT_VERIF; } /* (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 commentPtr) { // Store javadoc positions this.javadocStart = this.sourceParser.scanner.commentStarts[commentPtr]; this.javadocEnd = this.sourceParser.scanner.commentStops[commentPtr]-1; this.firstTagPosition = this.sourceParser.scanner.commentTagStarts[commentPtr]; this.validValuePositions = -1; this.invalidValuePositions = -1; // Init javadoc if necessary if (this.checkDocComment) { this.docComment = new Javadoc(javadocStart, javadocEnd); } else { this.docComment = null; } // If there's no tag in javadoc, return without parsing it if (this.firstTagPosition == 0) { switch (this.kind & PARSER_KIND) { case COMPIL_PARSER: case SOURCE_PARSER: return false; } } // Parse try { this.source = this.sourceParser.scanner.source; if (this.checkDocComment) { // Initialization this.scanner.lineEnds = this.sourceParser.scanner.lineEnds; this.scanner.linePtr = this.sourceParser.scanner.linePtr; this.lineEnds = this.scanner.lineEnds; commentParse(); } else { // Parse comment Scanner sourceScanner = this.sourceParser.scanner; int firstLineNumber = Util.getLineNumber(javadocStart, sourceScanner.lineEnds, 0, sourceScanner.linePtr); int lastLineNumber = Util.getLineNumber(javadocEnd, sourceScanner.lineEnds, 0, sourceScanner.linePtr); this.index = javadocStart +3; // scan line per line, since tags must be at beginning of lines only this.deprecated = false; 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) { case '*' : case '\u000c' : /* FORM FEED */ case ' ' : /* SPACE */ case '\t' : /* HORIZONTAL TABULATION */ case '\n' : /* LINE FEED */ case '\r' : /* CR */ // do nothing for space or '*' characters continue nextCharacter; case '@' : parseSimpleTag(); if (this.tagValue == TAG_DEPRECATED_VALUE) { if (this.abort) break nextCharacter; } } continue nextLine; } } return this.deprecated; } } finally { this.source = null; // release source as soon as finished } return this.deprecated; } /* (non-Javadoc) * @see org.eclipse.wst.jsdt.internal.compiler.parser.AbstractCommentParser#createArgumentReference(char[], java.lang.Object, int) */ protected Object createArgumentReference(char[] name, int dim, boolean isVarargs, 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 (isVarargs) { argTypeRef.bits |= ASTNode.IsVarArgs; // set isVarArgs } } 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.wst.jsdt.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.getMainTypeName(); typeRef = new JavadocImplicitTypeReference(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; field.tagValue = this.tagValue; return field; } catch (ClassCastException ex) { throw new InvalidInputException(); } } /* (non-Javadoc) * @see org.eclipse.wst.jsdt.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; int length = this.identifierLengthStack[0]; // may be > 0 for member class constructor reference if (typeRef == null) { char[] name = this.sourceParser.compilationUnit.getMainTypeName(); TypeDeclaration typeDecl = getParsedTypeDeclaration(); if (typeDecl != null) { name = typeDecl.name; } isConstructor = CharOperation.equals(this.identifierStack[length-1], name); typeRef = new JavadocImplicitTypeReference(name, this.memberStart); } else { if (typeRef instanceof JavadocSingleTypeReference) { char[] name = ((JavadocSingleTypeReference)typeRef).token; isConstructor = CharOperation.equals(this.identifierStack[length-1], name); } else if (typeRef instanceof JavadocQualifiedTypeReference) { char[][] tokens = ((JavadocQualifiedTypeReference)typeRef).tokens; int last = tokens.length-1; isConstructor = CharOperation.equals(this.identifierStack[length-1], tokens[last]); if (isConstructor) { boolean valid = true; if (valid) { for (int i=0; i<length-1 && valid; i++) { valid = CharOperation.equals(this.identifierStack[i], tokens[i]); } } if (!valid) { return null; } } } else { throw new InvalidInputException(); } } // Create node if (arguments == null) { if (isConstructor) { JavadocAllocationExpression allocation = new JavadocAllocationExpression(this.identifierPositionStack[length-1]); allocation.type = typeRef; allocation.tagValue = this.tagValue; allocation.sourceEnd = this.scanner.getCurrentTokenEndPosition(); if (length == 1) { allocation.qualification = new char[][] { this.identifierStack[0] }; } else { System.arraycopy(this.identifierStack, 0, allocation.qualification = new char[length][], 0, length); allocation.sourceStart = (int) (this.identifierPositionStack[0] >>> 32); } allocation.memberStart = this.memberStart; return allocation; } else { JavadocMessageSend msg = new JavadocMessageSend(this.identifierStack[length-1], this.identifierPositionStack[length-1]); msg.receiver = typeRef; msg.tagValue = this.tagValue; msg.sourceEnd = this.scanner.getCurrentTokenEndPosition(); return msg; } } else { JavadocArgumentExpression[] expressions = new JavadocArgumentExpression[arguments.size()]; arguments.toArray(expressions); if (isConstructor) { JavadocAllocationExpression allocation = new JavadocAllocationExpression(this.identifierPositionStack[length-1]); allocation.arguments = expressions; allocation.type = typeRef; allocation.tagValue = this.tagValue; allocation.sourceEnd = this.scanner.getCurrentTokenEndPosition(); if (length == 1) { allocation.qualification = new char[][] { this.identifierStack[0] }; } else { System.arraycopy(this.identifierStack, 0, allocation.qualification = new char[length][], 0, length); allocation.sourceStart = (int) (this.identifierPositionStack[0] >>> 32); } allocation.memberStart = this.memberStart; return allocation; } else { JavadocMessageSend msg = new JavadocMessageSend(this.identifierStack[length-1], this.identifierPositionStack[length-1], expressions); msg.receiver = typeRef; msg.tagValue = this.tagValue; msg.sourceEnd = this.scanner.getCurrentTokenEndPosition(); return msg; } } } catch (ClassCastException ex) { throw new InvalidInputException(); } } /* (non-Javadoc) * @see org.eclipse.wst.jsdt.internal.compiler.parser.AbstractCommentParser#createReturnStatement() */ protected Object createReturnStatement() { return new JavadocReturnStatement(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition()); } /* (non-Javadoc) * @see org.eclipse.wst.jsdt.internal.compiler.parser.AbstractCommentParser#parseTagName() */ protected void createTag() { this.tagValue = TAG_OTHERS_VALUE; } /* (non-Javadoc) * @see org.eclipse.wst.jsdt.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); } return typeRef; } /* * Get current parsed type declaration. */ protected TypeDeclaration getParsedTypeDeclaration() { int ptr = this.sourceParser.astPtr; while (ptr >= 0) { Object node = this.sourceParser.astStack[ptr]; if (node instanceof TypeDeclaration) { TypeDeclaration typeDecl = (TypeDeclaration) node; if (typeDecl.bodyEnd == 0) { // type declaration currenly parsed return typeDecl; } } ptr--; } return null; } /* * Parse @return tag declaration */ protected boolean parseReturn() { if (this.returnStatement == null) { Object[] type = parseTypeReference(); this.returnType = (TypeReference) (type != null && type.length > 0 ? type[0] : null); this.returnStatement = createReturnStatement(); if((this.kind & TEXT_VERIF) != 0 && type != null && type.length > 0 && type[0] != null) refreshReturnStatement(); return true; } if (this.reportProblems) { this.sourceParser.problemReporter().javadocDuplicatedReturnTag( this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition()); } return false; } protected void parseSimpleTag() { // Read first char // readChar() code is inlined to balance additional method call in checkDeprectation(int) char first = this.source[this.index++]; if (first == '\\' && this.source[this.index] == 'u') { int c1, c2, c3, c4; int pos = this.index; this.index++; while (this.source[this.index] == 'u') this.index++; if (!(((c1 = ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c1 < 0) || ((c2 = ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c2 < 0) || ((c3 = ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4 = ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) { first = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4); } else { this.index = pos; } } // switch on first tag char switch (first) { case 'd': if ((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. char c = readChar(); if (ScannerHelper.isWhitespace(c) || c == '*') { this.abort = true; this.deprecated = true; this.tagValue = TAG_DEPRECATED_VALUE; } } break; } } protected boolean parseTag(int previousPosition) throws InvalidInputException { boolean valid = false; // Read tag name int currentPosition = this.index; int token = readTokenAndConsume(); if (currentPosition != this.scanner.startPosition) { this.tagSourceStart = previousPosition; this.tagSourceEnd = currentPosition; if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(this.tagSourceStart, this.tagSourceEnd); return false; } if (this.index >= this.scanner.eofPosition) { this.tagSourceStart = previousPosition; this.tagSourceEnd = this.tokenPreviousPosition; if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(this.tagSourceStart, this.tagSourceEnd); return false; } this.tagSourceStart = this.scanner.getCurrentTokenStartPosition(); this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition(); char[] tagName = this.scanner.getCurrentIdentifierSource(); // Try to get tag name other than java identifier // (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660) if (this.scanner.currentCharacter != ' ' && !ScannerHelper.isWhitespace(this.scanner.currentCharacter)) { boolean validTag = true; tagNameToken: while (token != TerminalTokens.TokenNameEOF && this.index < this.scanner.eofPosition) { int length = tagName.length; // !, ", #, %, &, ', -, :, <, >, * chars and spaces are not allowed in tag names switch (this.scanner.currentCharacter) { case '}': case '*': // break for '*' as this is perhaps the end of comment (bug 65288) break tagNameToken; case '!': case '#': case '%': case '&': case '\'': case '"': case ':': case '<': case '>': case '@': validTag = false; this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition(); this.index = this.scanner.currentPosition; break; case '-': // allowed in tag names as this character is often used in doclets (bug 68087) System.arraycopy(tagName, 0, tagName = new char[length+1], 0, length); tagName[length] = this.scanner.currentCharacter; this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition(); this.index = this.scanner.currentPosition; break; default: if (this.scanner.currentCharacter == ' ' || ScannerHelper.isWhitespace(this.scanner.currentCharacter)) { break tagNameToken; } token = readTokenAndConsume(); char[] ident = this.scanner.getCurrentIdentifierSource(); System.arraycopy(tagName, 0, tagName = new char[length+ident.length], 0, length); System.arraycopy(ident, 0, tagName, length, ident.length); this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition(); break; } this.scanner.getNextChar(); } if (!validTag) { if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(this.tagSourceStart, this.tagSourceEnd); return false; } } int length = tagName.length; if (length == 0) return false; // may happen for some parser (completion for example) this.index = this.tagSourceEnd+1; this.scanner.currentPosition = this.tagSourceEnd+1; // Decide which parse to perform depending on tag name this.tagValue = NO_TAG_VALUE; switch (token) { case TerminalTokens.TokenNameIdentifier : switch (tagName[0]) { case 'a': if (length == TAG_ADDON_LENGTH && CharOperation.equals(TAG_ADDON, tagName)) { this.tagValue = TAG_ADDON_VALUE; valid=true; } else if (length == TAG_ALIAS_LENGTH && CharOperation.equals(TAG_ALIAS, tagName)) { this.tagValue = TAG_ALIAS_VALUE; valid=true; } else if (length == TAG_ARGUMENT_LENGTH && CharOperation.equals(TAG_ARGUMENT, tagName)) { this.tagValue = TAG_ARGUMENT_VALUE; valid=parseParam(); } break; case 'b': if (length == TAG_BASE_LENGTH && CharOperation.equals(TAG_BASE, tagName)) { this.tagValue = TAG_BASE_VALUE; valid=parseExtends(); } break; case 'c': if (length == TAG_CLASSDECRIPTION_LENGTH && CharOperation.equals(TAG_CLASSDECRIPTION, tagName)) { this.tagValue = TAG_CLASSDECRIPTION_VALUE; valid =true; } else if (length == TAG_CLASS_LENGTH && CharOperation.equals(TAG_CLASS, tagName)) { this.tagValue = TAG_CLASS_VALUE; valid =parseClass() ; } else if (length == TAG_CONSTRUCTOR_LENGTH && CharOperation.equals(TAG_CONSTRUCTOR, tagName)) { this.tagValue = TAG_CONSTRUCTOR_VALUE; this.isConstructor=true; valid =true; } break; case 'd': if (length == TAG_DEPRECATED_LENGTH && CharOperation.equals(TAG_DEPRECATED, tagName)) { this.deprecated = true; valid = true; this.tagValue = TAG_DEPRECATED_VALUE; } break; case 'e': if (length == TAG_EXCEPTION_LENGTH && CharOperation.equals(TAG_EXCEPTION, tagName)) { this.tagValue = TAG_EXCEPTION_VALUE; valid = parseThrows(); } else if (length == TAG_EXTENDS_LENGTH && CharOperation.equals(TAG_EXTENDS, tagName)) { this.tagValue = TAG_EXTENDS_VALUE; valid = parseExtends(); } else if (length == TAG_EXEC_LENGTH && CharOperation.equals(TAG_EXEC, tagName)) { this.tagValue = TAG_EXEC_VALUE; valid = true; } break; case 'f': if (length == TAG_FILEOVERVIEW_LENGTH && CharOperation.equals(TAG_FILEOVERVIEW, tagName)) { this.tagValue = TAG_FILEOVERVIEW_VALUE; valid = true; } else if (length == TAG_FINAL_LENGTH && CharOperation.equals(TAG_FINAL, tagName)) { this.tagValue = TAG_FINAL_VALUE; this.flags|=ClassFileConstants.AccFinal; valid = true; } break; case 'i': if (length == TAG_ID_LENGTH && CharOperation.equals(TAG_ID, tagName)) { this.tagValue = TAG_ID_VALUE; valid = true; } else if (length == TAG_IGNORE_LENGTH && CharOperation.equals(TAG_IGNORE, tagName)) { this.tagValue = TAG_IGNORE_VALUE; valid = true; } break; case 'l': if (length == TAG_LINK_LENGTH && CharOperation.equals(TAG_LINK, tagName)) { this.tagValue = TAG_LINK_VALUE; if (this.inlineTagStarted || (this.kind & COMPLETION_PARSER) != 0) { valid= parseReference(); } else { // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290 // Cannot have @link outside inline comment valid = false; if (this.reportProblems) { this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd); } } } break; case 'm': if (length == TAG_MEMBER_LENGTH && CharOperation.equals(TAG_MEMBER, tagName)) { this.tagValue = TAG_MEMBER_VALUE; valid = parseMember(); } else if (length == TAG_MEMBEROF_LENGTH && CharOperation.equals(TAG_MEMBEROF, tagName)) { this.tagValue = TAG_MEMBEROF_VALUE; valid = parseMember(); } else if (length == TAG_METHOD_LENGTH && CharOperation.equals(TAG_METHOD, tagName)) { this.tagValue = TAG_METHOD_VALUE; valid = parseMethod(); } break; case 'n': if (length == TAG_NAMESPACE_LENGTH && CharOperation.equals(TAG_NAMESPACE, tagName)) { this.tagValue = TAG_NAMESPACE_VALUE; valid = parseNamespace(); } break; case 'p': if (length == TAG_PARAM_LENGTH && CharOperation.equals(TAG_PARAM, tagName)) { this.tagValue = TAG_PARAM_VALUE; valid = parseParam(); } else if (length == TAG_PRIVATE_LENGTH && CharOperation.equals(TAG_PRIVATE, tagName)) { this.tagValue = TAG_PRIVATE_VALUE; this.flags|=ClassFileConstants.AccPrivate; valid = true; } else if (length == TAG_PROJECT_DESCRIPTION_LENGTH && CharOperation.equals(TAG_PROJECT_DESCRIPTION, tagName)) { this.tagValue = TAG_PROJECT_DESCRIPTION_VALUE; valid = true; } else if (length == TAG_PROPERTY_LENGTH && CharOperation.equals(TAG_PROPERTY, tagName)) { this.tagValue = TAG_PROPERTY_VALUE; valid = parseProperty(); } else if (length == TAG_PROTECTED_LENGTH && CharOperation.equals(TAG_PROTECTED, tagName)) { this.tagValue = TAG_PRIVATE_VALUE; this.flags|=ClassFileConstants.AccProtected; valid = true; } break; case 'r': if (length == TAG_RETURNS_LENGTH && CharOperation.equals(TAG_RETURNS, tagName)) { this.tagValue = TAG_RETURNS_VALUE; valid = parseReturn(); } else if (length == TAG_RETURN_LENGTH && CharOperation.equals(TAG_RETURN, tagName)) { this.tagValue = TAG_RETURNS_VALUE; valid = parseReturn(); } else if (length == TAG_REQUIRES_LENGTH && CharOperation.equals(TAG_REQUIRES, tagName)) { this.tagValue = TAG_REQUIRES_VALUE; valid = parseRequires(); } break; case 's': if (length == TAG_SEE_LENGTH && CharOperation.equals(TAG_SEE, tagName)) { if (this.inlineTagStarted) { // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290 // Cannot have @see inside inline comment valid = false; if (this.reportProblems) { this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd); } } else { this.tagValue = TAG_SEE_VALUE; valid = parseReference(); } } else if (length == TAG_SDOC_LENGTH && CharOperation.equals(TAG_SDOC, tagName)) { this.tagValue = TAG_SDOC_VALUE; valid = true; } break; case 't': if (length == TAG_TYPE_LENGTH && CharOperation.equals(TAG_TYPE, tagName)) { this.tagValue = TAG_TYPE_VALUE; valid=parseType(); } break; default: createTag(); break; } break; case TerminalTokens.TokenNamereturn : this.tagValue = TAG_RETURN_VALUE; valid = parseReturn(); /* verify characters after return tag (we're expecting text description) if(!verifyCharsAfterReturnTag(this.index)) { if (this.sourceParser != null) { int end = this.starPosition == -1 || this.lineEnd<this.starPosition ? this.lineEnd : this.starPosition; this.sourceParser.problemReporter().javadocEmptyReturnTag(this.tagSourceStart, end); } } */ break; case TerminalTokens.TokenNamethrows : this.tagValue = TAG_THROWS_VALUE; valid = parseThrows(); break; case TerminalTokens.TokenNameclass : this.tagValue = TAG_CLASS_VALUE; valid=true; break; case TerminalTokens.TokenNameextends : this.tagValue = TAG_EXTENDS_VALUE; valid=parseExtends(); break; case TerminalTokens.TokenNameprivate : this.tagValue = TAG_PRIVATE_VALUE; this.flags|=ClassFileConstants.AccPrivate; valid=true; break; case TerminalTokens.TokenNameprotected : this.tagValue = TAG_PROTECTED_VALUE; this.flags|=ClassFileConstants.AccProtected; valid=true; break; case TerminalTokens.TokenNamefinal : this.tagValue = TAG_FINAL_VALUE; this.flags|=ClassFileConstants.AccFinal; valid=true; break; } this.textStart = this.index; return valid; } private boolean parseExtends() throws InvalidInputException { this.extendsType=(TypeReference)parseQualifiedName(true); if (extendsType != null) this.isConstructor = true; return this.extendsType!=null; } private boolean parseType() throws InvalidInputException { this.returnType=(TypeReference)parseQualifiedName(true); return this.returnType!=null; } private boolean parseNamespace() throws InvalidInputException { this.namespace=(TypeReference) parseQualifiedName(true); return this.namespace!=null; } private boolean parseProperty() throws InvalidInputException { // Store current state int start = this.tagSourceStart; int end = this.tagSourceEnd; boolean tokenWhiteSpace = this.scanner.tokenizeWhiteSpace; this.scanner.tokenizeWhiteSpace = true; Object []typeReference=null; // Get first non whitespace token this.identifierPtr = -1; this.identifierLengthPtr = -1; boolean hasMultiLines = this.scanner.currentPosition > (this.lineEnd+1); boolean valid = true, empty = true; boolean isParmType=false; int token = -1; nextToken: while (true) { this.currentTokenType = -1; try { token = readToken(); } catch (InvalidInputException e) { valid = false; } switch (token) { case TerminalTokens.TokenNameIdentifier : if (valid) { // store param name id pushIdentifier(true, false); start = this.scanner.getCurrentTokenStartPosition(); end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition(); break nextToken; } // fall through next case to report error default: if (valid && !hasMultiLines) start = this.scanner.getCurrentTokenStartPosition(); valid = false; if (!hasMultiLines) { empty = false; end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition(); break; } end = this.lineEnd; // when several lines, fall through next case to report problem immediately case TerminalTokens.TokenNameWHITESPACE: if (this.scanner.currentPosition > (this.lineEnd+1)) hasMultiLines = true; if (valid) break; // if not valid fall through next case to report error case TerminalTokens.TokenNameEOF: if (this.reportProblems) if (empty) this.sourceParser.problemReporter().javadocMissingParamName(start, end, this.sourceParser.modifiers); else this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end); this.currentTokenType = -1; this.scanner.tokenizeWhiteSpace = tokenWhiteSpace; return false; case TerminalTokens.TokenNameLBRACE: this.scanner.tokenizeWhiteSpace = false; typeReference=parseTypeReference(); isParmType=true; this.identifierPtr = -1; this.identifierLengthPtr = -1; this.scanner.tokenizeWhiteSpace = true; break; } } // Verify that tag name is well followed by white spaces if (valid) { this.currentTokenType = -1; int restart = this.scanner.currentPosition; try { token = readToken(); } catch (InvalidInputException e) { valid = false; } if (token == TerminalTokens.TokenNameWHITESPACE) { this.scanner.currentPosition = restart; this.index = restart; this.scanner.tokenizeWhiteSpace = tokenWhiteSpace; valid= pushParamName(false); if (valid ) { JavadocSingleNameReference nameRef=(JavadocSingleNameReference)this.astStack[this.astPtr]; TypeReference [] refs=null; if (typeReference!=null && isParmType) { refs = new TypeReference[typeReference.length]; System.arraycopy(typeReference, 0, refs, 0, typeReference.length); } nameRef.types=refs; this.property=nameRef; } return valid; } } // Report problem this.currentTokenType = -1; end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition(); while ((token=readToken()) != TerminalTokens.TokenNameWHITESPACE && token != TerminalTokens.TokenNameEOF) { this.currentTokenType = -1; end = hasMultiLines ? this.lineEnd: this.scanner.getCurrentTokenEndPosition(); } if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end); this.scanner.currentPosition = start; this.index = start; this.currentTokenType = -1; this.scanner.tokenizeWhiteSpace = tokenWhiteSpace; return false; } private boolean parseClass() throws InvalidInputException { this.classDef=(TypeReference) parseQualifiedName(true); return this.classDef!=null; } private boolean parseMethod() throws InvalidInputException { this.methodDef=(TypeReference) parseQualifiedName(true); return this.methodDef!=null; } private boolean parseMember() throws InvalidInputException { this.memberOf=(TypeReference) parseQualifiedName(true); return this.memberOf!=null; } /* * Push a param name in ast node stack. */ protected boolean pushParamName(boolean isTypeParam) { // Create param reference ASTNode nameRef = null; if (isTypeParam) { JavadocSingleTypeReference ref = new JavadocSingleTypeReference(this.identifierStack[1], this.identifierPositionStack[1], this.tagSourceStart, this.tagSourceEnd); nameRef = ref; } else { JavadocSingleNameReference ref = new JavadocSingleNameReference(this.identifierStack[0], this.identifierPositionStack[0], this.tagSourceStart, this.tagSourceEnd); nameRef = ref; } // 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.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd); // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600 // store invalid param references in specific array if (this.invalidParamReferencesPtr == -1l) { this.invalidParamReferencesStack = new JavadocSingleNameReference[10]; } int stackLength = this.invalidParamReferencesStack.length; if (++this.invalidParamReferencesPtr >= stackLength) { System.arraycopy( this.invalidParamReferencesStack, 0, this.invalidParamReferencesStack = new JavadocSingleNameReference[stackLength + AST_STACK_INCREMENT], 0, stackLength); } this.invalidParamReferencesStack[this.invalidParamReferencesPtr] = 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) { 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; } /* * Push a throws type ref in ast node stack. */ protected boolean pushThrowName(Object typeRef) { 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; } /* * Refresh return statement */ protected void refreshReturnStatement() { ((JavadocReturnStatement) this.returnStatement).bits &= ~ASTNode.Empty; } 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(); } /* * Fill associated comment fields with ast nodes information stored in stack. */ protected void updateDocComment() { this.docComment.modifiers=this.flags; this.docComment.namespace=this.namespace; this.docComment.memberOf=this.memberOf; this.docComment.returnType=this.returnType; this.docComment.extendsType=this.extendsType; this.docComment.classDef=this.classDef; this.docComment.methodDef=this.methodDef; this.docComment.property=this.property; this.docComment.isConstructor=this.isConstructor; // Set positions this.docComment.inheritedPositions = this.inheritedPositions; this.docComment.valuePositions = this.validValuePositions != -1 ? this.validValuePositions : this.invalidValuePositions; // Set return node if present if (this.returnStatement != null) { this.docComment.returnStatement = (JavadocReturnStatement) this.returnStatement; } // Copy array of invalid syntax param tags if (this.invalidParamReferencesPtr >= 0) { this.docComment.invalidParameters = new JavadocSingleNameReference[this.invalidParamReferencesPtr+1]; System.arraycopy(this.invalidParamReferencesStack, 0, this.docComment.invalidParameters, 0, this.invalidParamReferencesPtr+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.seeReferences = new Expression[sizes[SEE_TAG_EXPECTED_ORDER]]; this.docComment.exceptionReferences = new TypeReference[sizes[THROWS_TAG_EXPECTED_ORDER]]; int paramRefPtr = sizes[PARAM_TAG_EXPECTED_ORDER]; this.docComment.paramReferences = new JavadocSingleNameReference[paramRefPtr]; // 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 switch(ptr) { case SEE_TAG_EXPECTED_ORDER: int size = this.astLengthStack[this.astLengthPtr--]; for (int i=0; i<size; i++) { this.docComment.seeReferences[--sizes[ptr]] = (Expression) this.astStack[this.astPtr--]; } break; // Then continuing with class names (eg. TypeReference) coming from @throw/@exception declarations case THROWS_TAG_EXPECTED_ORDER: size = this.astLengthStack[this.astLengthPtr--]; for (int i=0; i<size; i++) { this.docComment.exceptionReferences[--sizes[ptr]] = (TypeReference) this.astStack[this.astPtr--]; } break; // Finally, finishing with parameters nales (ie. Argument) coming from @param declaration case PARAM_TAG_EXPECTED_ORDER: size = this.astLengthStack[this.astLengthPtr--]; for (int i=0; i<size; i++) { Expression reference = (Expression) this.astStack[this.astPtr--]; if (reference instanceof JavadocSingleNameReference) this.docComment.paramReferences[--paramRefPtr] = (JavadocSingleNameReference) reference; } break; } } // Resize param tag references arrays if (paramRefPtr != 0) { // there's no type parameters references // there both of references => resize arrays int size = sizes[PARAM_TAG_EXPECTED_ORDER]; System.arraycopy(this.docComment.paramReferences, paramRefPtr, this.docComment.paramReferences = new JavadocSingleNameReference[size - paramRefPtr], 0, size - paramRefPtr); } } protected void createParamType(Object[] typeReference) { JavadocSingleNameReference nameRef=(JavadocSingleNameReference)this.astStack[this.astPtr]; TypeReference [] refs=null; if (typeReference!=null) { refs = new TypeReference[typeReference.length]; System.arraycopy(typeReference, 0, refs, 0, typeReference.length); } nameRef.types=refs; } protected boolean parseRequires() { return true; } }