+++ /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.ArrayList;
-import java.util.List;
-
-import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.core.compiler.InvalidInputException;
-import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
-
-/**
- * Parser specialized for decoding javadoc comments
- */
-public abstract class AbstractCommentParser {
-
- // recognized tags
- public static final char[] TAG_DEPRECATED = "deprecated".toCharArray(); //$NON-NLS-1$
- public static final char[] TAG_PARAM = "param".toCharArray(); //$NON-NLS-1$
- public static final char[] TAG_RETURN = "return".toCharArray(); //$NON-NLS-1$
- public static final char[] TAG_THROWS = "throws".toCharArray(); //$NON-NLS-1$
- public static final char[] TAG_EXCEPTION = "exception".toCharArray(); //$NON-NLS-1$
- public static final char[] TAG_SEE = "see".toCharArray(); //$NON-NLS-1$
- public static final char[] TAG_LINK = "link".toCharArray(); //$NON-NLS-1$
- public static final char[] TAG_LINKPLAIN = "linkplain".toCharArray(); //$NON-NLS-1$
- public static final char[] TAG_INHERITDOC = "inheritDoc".toCharArray(); //$NON-NLS-1$
-
- // tags expected positions
- public final static int ORDERED_TAGS_NUMBER = 3;
- public final static int PARAM_TAG_EXPECTED_ORDER = 0;
- public final static int THROWS_TAG_EXPECTED_ORDER = 1;
- public final static int SEE_TAG_EXPECTED_ORDER = 2;
-
- // Kind of comment parser
- public final static int COMPIL_PARSER = 0x00000001;
- public final static int DOM_PARSER = 0x00000002;
-
- // Public fields
- public Scanner scanner;
- public boolean checkDocComment = false;
-
- // Protected fields
- protected boolean inherited, deprecated;
- protected char[] source;
- protected int index, endComment, lineEnd;
- protected int tokenPreviousPosition, lastIdentifierEndPosition, starPosition;
- protected int textStart, memberStart;
- protected int tagSourceStart, tagSourceEnd;
- protected int inlineTagStart;
- protected Parser sourceParser;
- protected Object returnStatement;
- protected boolean lineStarted = false, inlineTagStarted = false;
- protected int kind;
- protected int[] lineEnds;
-
- // Private fields
- private int currentTokenType = -1;
-
- // Line pointers
- private int linePtr, lastLinePtr;
-
- // Identifier stack
- protected int identifierPtr;
- protected char[][] identifierStack;
- protected int identifierLengthPtr;
- protected int[] identifierLengthStack;
- protected long[] identifierPositionStack;
- // Ast stack
- protected static int AstStackIncrement = 10;
- protected int astPtr;
- protected Object[] astStack;
- protected int astLengthPtr;
- protected int[] astLengthStack;
-
- protected AbstractCommentParser(Parser sourceParser) {
- this.sourceParser = sourceParser;
- this.scanner = new Scanner(false, false, false, ClassFileConstants.JDK1_3, null, null, true/*taskCaseSensitive*/);
- this.identifierStack = new char[20][];
- this.identifierPositionStack = new long[20];
- this.identifierLengthStack = new int[10];
- this.astStack = new Object[30];
- this.astLengthStack = new int[20];
- }
-
- /* (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.
- */
- protected boolean parseComment(int javadocStart, int javadocEnd) {
-
- boolean validComment = true;
- try {
- // Init scanner position
- this.scanner.resetTo(javadocStart, javadocEnd);
- this.endComment = javadocEnd;
- this.index = javadocStart;
- readChar(); // starting '/'
- int previousPosition = this.index;
- readChar(); // first '*'
- char nextCharacter= readChar(); // second '*'
-
- // Init local variables
- this.astLengthPtr = -1;
- this.astPtr = -1;
- this.currentTokenType = -1;
- this.inlineTagStarted = false;
- this.inlineTagStart = -1;
- this.lineStarted = false;
- this.returnStatement = null;
- this.inherited = false;
- this.deprecated = false;
- this.linePtr = getLineNumber(javadocStart);
- this.lastLinePtr = getLineNumber(javadocEnd);
- this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.endComment : this.scanner.getLineEnd(this.linePtr);
- this.textStart = -1;
- char previousChar = 0;
- int invalidTagLineEnd = -1;
- int invalidInlineTagLineEnd = -1;
-
- // Loop on each comment character
- while (this.index < this.endComment) {
- previousPosition = this.index;
- previousChar = nextCharacter;
-
- // Calculate line end (cannot use this.scanner.linePtr as scanner does not parse line ends again)
- if (this.index > (this.lineEnd+1)) {
- updateLineEnd();
- }
-
- // Read next char only if token was consumed
- if (this.currentTokenType < 0) {
- nextCharacter = readChar(); // consider unicodes
- } else {
- previousPosition = this.scanner.getCurrentTokenStartPosition();
- switch (this.currentTokenType) {
- case TerminalTokens.TokenNameRBRACE:
- nextCharacter = '}';
- break;
- case TerminalTokens.TokenNameMULTIPLY:
- nextCharacter = '*';
- break;
- default:
- nextCharacter = this.scanner.currentCharacter;
- }
- consumeToken();
- }
-
- if (this.index >= this.endComment) {
- break;
- }
-
- switch (nextCharacter) {
- case '@' :
- boolean valid = false;
- // Start tag parsing only if we have a java identifier start character and if we are on line beginning or at inline tag beginning
- if ((!this.lineStarted || previousChar == '{')) {
- this.lineStarted = true;
- if (this.inlineTagStarted) {
- this.inlineTagStarted = false;
- // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
- // Cannot have @ inside inline comment
- if (this.sourceParser != null) {
- int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
- this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
- }
- validComment = false;
- if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
- pushText(this.textStart, previousPosition);
- }
- if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
- }
- if (previousChar == '{') {
- if (this.textStart != -1 && this.textStart < this.inlineTagStart) {
- pushText(this.textStart, this.inlineTagStart);
- }
- this.inlineTagStarted = true;
- invalidInlineTagLineEnd = this.lineEnd;
- } else if (this.textStart != -1 && this.textStart < invalidTagLineEnd) {
- pushText(this.textStart, invalidTagLineEnd);
- }
- this.scanner.resetTo(this.index, this.endComment);
- this.currentTokenType = -1; // flush token cache at line begin
- try {
- int token = readTokenAndConsume();
- this.tagSourceStart = this.scanner.getCurrentTokenStartPosition();
- this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
- char[] tag = this.scanner.getCurrentIdentifierSource(); // first token is either an identifier or a keyword
- if (this.kind == DOM_PARSER) {
- // For DOM parser, try to get tag name other than java identifier
- // (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51660)
- int tk = token;
- int le = this.lineEnd;
- char pc = peekChar();
- tagNameToken: while (tk != TerminalTokens.TokenNameEOF) {
- this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
- token = tk;
- // !, ", #, %, &, ', -, :, <, >, * chars and spaces are not allowed in tag names
- switch (pc) {
- case '}':
- case '!':
- case '#':
- case '%':
- case '&':
- case '\'':
- case ':':
- // case '-': allowed in tag names as this character is often used in doclets (bug 68087)
- case '<':
- case '>':
- case '*': // break for '*' as this is perhaps the end of comment (bug 65288)
- break tagNameToken;
- default:
- if (pc == ' ' || Character.isWhitespace(pc)) break tagNameToken;
- }
- tk = readTokenAndConsume();
- pc = peekChar();
- }
- int length = this.tagSourceEnd-this.tagSourceStart+1;
- tag = new char[length];
- System.arraycopy(this.source, this.tagSourceStart, tag, 0, length);
- this.index = this.tagSourceEnd+1;
- this.scanner.currentPosition = this.tagSourceEnd+1;
- this.tagSourceStart = previousPosition;
- this.lineEnd = le;
- }
- switch (token) {
- case TerminalTokens.TokenNameIdentifier :
- if (CharOperation.equals(tag, TAG_DEPRECATED)) {
- this.deprecated = true;
- if (this.kind == DOM_PARSER) {
- valid = parseTag();
- } else {
- valid = true;
- }
- } else if (CharOperation.equals(tag, TAG_INHERITDOC)) {
- // inhibits inherited flag when tags have been already stored
- // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51606
- // Note that for DOM_PARSER, nodes stack may be not empty even no '@' tag
- // was encountered in comment. But it cannot be the case for COMPILER_PARSER
- // and so is enough as it is only this parser which signals the missing tag warnings...
- this.inherited = this.astPtr==-1;
- if (this.kind == DOM_PARSER) {
- valid = parseTag();
- } else {
- valid = true;
- }
- } else if (CharOperation.equals(tag, TAG_PARAM)) {
- valid = parseParam();
- } else if (CharOperation.equals(tag, TAG_EXCEPTION)) {
- valid = parseThrows(false);
- } else if (CharOperation.equals(tag, TAG_SEE)) {
- if (this.inlineTagStarted) {
- // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
- // Cannot have @see inside inline comment
- valid = false;
- if (this.sourceParser != null)
- this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
- } else {
- valid = parseSee(false);
- }
- } else if (CharOperation.equals(tag, TAG_LINK)) {
- if (this.inlineTagStarted) {
- valid = parseSee(false);
- } else {
- // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
- // Cannot have @link outside inline comment
- valid = false;
- if (this.sourceParser != null)
- this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
- }
- } else if (CharOperation.equals(tag, TAG_LINKPLAIN)) {
- if (this.inlineTagStarted) {
- valid = parseSee(true);
- } else {
- valid = parseTag();
- }
- } else {
- valid = parseTag();
- }
- break;
- case TerminalTokens.TokenNamereturn :
- 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().javadocInvalidTag(this.tagSourceStart, end);
- }
- }
- break;
- case TerminalTokens.TokenNamethrows :
- valid = parseThrows(true);
- break;
- default:
- if (this.kind == DOM_PARSER) {
- switch (token) {
- case TerminalTokens.TokenNameabstract:
- case TerminalTokens.TokenNameassert:
- case TerminalTokens.TokenNameboolean:
- case TerminalTokens.TokenNamebreak:
- case TerminalTokens.TokenNamebyte:
- case TerminalTokens.TokenNamecase:
- case TerminalTokens.TokenNamecatch:
- case TerminalTokens.TokenNamechar:
- case TerminalTokens.TokenNameclass:
- case TerminalTokens.TokenNamecontinue:
- case TerminalTokens.TokenNamedefault:
- case TerminalTokens.TokenNamedo:
- case TerminalTokens.TokenNamedouble:
- case TerminalTokens.TokenNameelse:
- case TerminalTokens.TokenNameextends:
- case TerminalTokens.TokenNamefalse:
- case TerminalTokens.TokenNamefinal:
- case TerminalTokens.TokenNamefinally:
- case TerminalTokens.TokenNamefloat:
- case TerminalTokens.TokenNamefor:
- case TerminalTokens.TokenNameif:
- case TerminalTokens.TokenNameimplements:
- case TerminalTokens.TokenNameimport:
- case TerminalTokens.TokenNameinstanceof:
- case TerminalTokens.TokenNameint:
- case TerminalTokens.TokenNameinterface:
- case TerminalTokens.TokenNamelong:
- case TerminalTokens.TokenNamenative:
- case TerminalTokens.TokenNamenew:
- case TerminalTokens.TokenNamenull:
- case TerminalTokens.TokenNamepackage:
- case TerminalTokens.TokenNameprivate:
- case TerminalTokens.TokenNameprotected:
- case TerminalTokens.TokenNamepublic:
- case TerminalTokens.TokenNameshort:
- case TerminalTokens.TokenNamestatic:
- case TerminalTokens.TokenNamestrictfp:
- case TerminalTokens.TokenNamesuper:
- case TerminalTokens.TokenNameswitch:
- case TerminalTokens.TokenNamesynchronized:
- case TerminalTokens.TokenNamethis:
- case TerminalTokens.TokenNamethrow:
- case TerminalTokens.TokenNametransient:
- case TerminalTokens.TokenNametrue:
- case TerminalTokens.TokenNametry:
- case TerminalTokens.TokenNamevoid:
- case TerminalTokens.TokenNamevolatile:
- case TerminalTokens.TokenNamewhile:
- valid = parseTag();
- break;
- }
- }
- }
- this.textStart = this.index;
- if (!valid) {
- // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
- // do not stop the inline tag when error is encountered to get text after
- validComment = false;
- // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
- // for DOM AST node, store tag as text in case of invalid syntax
- if (this.kind == DOM_PARSER) {
- parseTag();
- this.textStart = this.tagSourceEnd+1;
- invalidTagLineEnd = this.lineEnd;
- }
- }
- } catch (InvalidInputException e) {
- consumeToken();
- }
- }
- break;
- case '\r':
- case '\n':
- if (this.lineStarted && this.textStart < previousPosition) {
- pushText(this.textStart, previousPosition);
- }
- this.lineStarted = false;
- // Fix bug 51650
- this.textStart = -1;
- break;
- case '}' :
- if (this.inlineTagStarted) {
- if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
- pushText(this.textStart, previousPosition);
- }
- if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
- this.textStart = this.index;
- this.inlineTagStarted = false;
- } else {
- if (!this.lineStarted) {
- this.textStart = previousPosition;
- }
- }
- this.lineStarted = true;
- break;
- case '{' :
- if (this.inlineTagStarted) {
- this.inlineTagStarted = false;
- // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
- // Cannot have opening brace in inline comment
- if (this.sourceParser != null) {
- int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
- this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
- }
- if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
- pushText(this.textStart, previousPosition);
- }
- if (this.kind == DOM_PARSER) refreshInlineTagPosition(previousPosition);
- }
- if (!this.lineStarted) {
- this.textStart = previousPosition;
- }
- this.lineStarted = true;
- this.inlineTagStart = previousPosition;
- break;
- case '*' :
- case '\u000c' : /* FORM FEED */
- case ' ' : /* SPACE */
- case '\t' : /* HORIZONTAL TABULATION */
- // do nothing for space or '*' characters
- break;
- default :
- if (!this.lineStarted) {
- this.textStart = previousPosition;
- }
- this.lineStarted = true;
- break;
- }
- }
- // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
- // Cannot leave comment inside inline comment
- if (this.inlineTagStarted) {
- this.inlineTagStarted = false;
- if (this.sourceParser != null) {
- int end = previousPosition<invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd;
- if (this.index >= this.endComment) end = invalidInlineTagLineEnd;
- this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end);
- }
- if (this.lineStarted && this.textStart != -1 && this.textStart < previousPosition) {
- pushText(this.textStart, previousPosition);
- }
- if (this.kind == DOM_PARSER) {
- refreshInlineTagPosition(previousPosition);
- }
- } else if (this.lineStarted && this.textStart < previousPosition) {
- pushText(this.textStart, previousPosition);
- }
- updateDocComment();
- } catch (Exception ex) {
- validComment = false;
- }
- return validComment;
- }
-
- private void consumeToken() {
- this.currentTokenType = -1; // flush token cache
- updateLineEnd();
- }
-
- protected abstract Object createArgumentReference(char[] name, int dim, Object typeRef, long[] dimPos, long argNamePos) throws InvalidInputException;
- protected abstract Object createFieldReference(Object receiver) throws InvalidInputException;
- protected abstract Object createMethodReference(Object receiver, List arguments) throws InvalidInputException;
- protected Object createReturnStatement() { return null; }
- protected abstract Object createTypeReference(int primitiveToken);
-
- private int getEndPosition() {
- if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
- return this.lineEnd;
- } else {
- return this.scanner.getCurrentTokenEndPosition();
- }
- }
-
- /*
- * Search the source position corresponding to the end of a given line number.
- * Warning: returned position is 1-based index!
- * @see Scanner#getLineEnd(int) We cannot directly use this method
- * when linePtr field is not initialized.
- *
- private int getLineEnd(int lineNumber) {
-
- if (this.scanner.linePtr != -1) {
- return this.scanner.getLineEnd(lineNumber);
- }
- if (this.lineEnds == null)
- return -1;
- if (lineNumber > this.lineEnds.length+1)
- return -1;
- if (lineNumber <= 0)
- return -1;
- if (lineNumber == this.lineEnds.length + 1)
- return this.scanner.eofPosition;
- return this.lineEnds[lineNumber-1]; // next line start one character behind the lineEnd of the previous line
- }
- */
-
- /**
- * Search the line number corresponding to a specific position.
- * Warning: returned position is 1-based index!
- * @see Scanner#getLineNumber(int) We cannot directly use this method
- * when linePtr field is not initialized.
- */
- private int getLineNumber(int position) {
-
- if (this.scanner.linePtr != -1) {
- return this.scanner.getLineNumber(position);
- }
- if (this.lineEnds == null)
- return 1;
- int length = this.lineEnds.length;
- if (length == 0)
- return 1;
- int g = 0, d = length - 1;
- int m = 0;
- while (g <= d) {
- m = (g + d) /2;
- if (position < this.lineEnds[m]) {
- d = m-1;
- } else if (position > this.lineEnds[m]) {
- g = m+1;
- } else {
- return m + 1;
- }
- }
- if (position < this.lineEnds[m]) {
- return m+1;
- }
- return m+2;
- }
-
- /*
- * Parse argument in @see tag method reference
- */
- private Object parseArguments(Object receiver) throws InvalidInputException {
-
- // Init
- int modulo = 0; // should be 2 for (Type,Type,...) or 3 for (Type arg,Type arg,...)
- int iToken = 0;
- char[] argName = null;
- List arguments = new ArrayList(10);
- int start = this.scanner.getCurrentTokenStartPosition();
-
- // Parse arguments declaration if method reference
- nextArg : while (this.index < this.scanner.eofPosition) {
-
- // Read argument type reference
- Object typeRef;
- try {
- typeRef = parseQualifiedName(false);
- } catch (InvalidInputException e) {
- break nextArg;
- }
- boolean firstArg = modulo == 0;
- if (firstArg) { // verify position
- if (iToken != 0)
- break nextArg;
- } else if ((iToken % modulo) != 0) {
- break nextArg;
- }
- if (typeRef == null) {
- if (firstArg && this.currentTokenType == TerminalTokens.TokenNameRPAREN) {
- // verify characters after arguments declaration (expecting white space or end comment)
- if (!verifySpaceOrEndComment()) {
- int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
- if (this.source[end]=='\n') end--;
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
- return null;
- }
- this.lineStarted = true;
- return createMethodReference(receiver, null);
- }
- break nextArg;
- }
- iToken++;
-
- // Read possible array declaration
- int dim = 0;
- long[] dimPositions = new long[20]; // assume that there won't be more than 20 dimensions...
- if (readToken() == TerminalTokens.TokenNameLBRACKET) {
- int dimStart = this.scanner.getCurrentTokenStartPosition();
- while (readToken() == TerminalTokens.TokenNameLBRACKET) {
- consumeToken();
- if (readToken() != TerminalTokens.TokenNameRBRACKET) {
- break nextArg;
- }
- consumeToken();
- dimPositions[dim++] = (((long) dimStart) << 32) + this.scanner.getCurrentTokenEndPosition();
- }
- }
-
- // Read argument name
- long argNamePos = -1;
- if (readToken() == TerminalTokens.TokenNameIdentifier) {
- consumeToken();
- if (firstArg) { // verify position
- if (iToken != 1)
- break nextArg;
- } else if ((iToken % modulo) != 1) {
- break nextArg;
- }
- if (argName == null) { // verify that all arguments name are declared
- if (!firstArg) {
- break nextArg;
- }
- }
- argName = this.scanner.getCurrentIdentifierSource();
- argNamePos = (((long)this.scanner.getCurrentTokenStartPosition())<<32)+this.scanner.getCurrentTokenEndPosition();
- iToken++;
- } else if (argName != null) { // verify that no argument name is declared
- break nextArg;
- }
-
- // Verify token position
- if (firstArg) {
- modulo = iToken + 1;
- } else {
- if ((iToken % modulo) != (modulo - 1)) {
- break nextArg;
- }
- }
-
- // Read separator or end arguments declaration
- int token = readToken();
- char[] name = argName == null ? new char[0] : argName;
- if (token == TerminalTokens.TokenNameCOMMA) {
- // Create new argument
- Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
- arguments.add(argument);
- consumeToken();
- iToken++;
- } else if (token == TerminalTokens.TokenNameRPAREN) {
- // verify characters after arguments declaration (expecting white space or end comment)
- if (!verifySpaceOrEndComment()) {
- int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
- if (this.source[end]=='\n') end--;
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
- return null;
- }
- // Create new argument
- Object argument = createArgumentReference(name, dim, typeRef, dimPositions, argNamePos);
- arguments.add(argument);
- consumeToken();
- return createMethodReference(receiver, arguments);
- } else {
- break nextArg;
- }
- }
-
- // Something wrong happened => Invalid input
- throw new InvalidInputException();
- }
-
- /*
- * Parse an URL link reference in @see tag
- */
- private boolean parseHref() throws InvalidInputException {
- int start = this.scanner.getCurrentTokenStartPosition();
- if (Character.toLowerCase(readChar()) == 'a') {
- this.scanner.currentPosition = this.index;
- if (readToken() == TerminalTokens.TokenNameIdentifier) {
- this.currentTokenType = -1; // do not update line end
- try {
- if (CharOperation.equals(this.scanner.getCurrentIdentifierSource(), new char[]{'h', 'r', 'e', 'f'}, false) &&
- readToken() == TerminalTokens.TokenNameEQUAL) {
- this.currentTokenType = -1; // do not update line end
- if (readToken() == TerminalTokens.TokenNameStringLiteral) {
- this.currentTokenType = -1; // do not update line end
- // Skip all characters after string literal until closing '>' (see bug 68726)
- while (this.index <= this.lineEnd && readToken() != TerminalTokens.TokenNameGREATER) {
- this.currentTokenType = -1; // do not update line end
- }
- if (this.currentTokenType == TerminalTokens.TokenNameGREATER) {
- consumeToken(); // update line end as new lines are allowed in URL description
- while (readToken() != TerminalTokens.TokenNameLESS) {
- if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@') {
- // Reset position: we want to rescan last token
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
- // Signal syntax error
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
- return false;
- }
- consumeToken();
- }
- this.currentTokenType = -1; // do not update line end
- if (readChar() == '/') {
- if (Character.toLowerCase(readChar()) == 'a') {
- if (readChar() == '>') {
- // Valid href
- return true;
- }
- }
- }
- }
- }
- }
- } catch (InvalidInputException ex) {
- // Do nothing as we want to keep positions for error message
- }
- }
- }
- // Reset position: we want to rescan last token
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
- // Signal syntax error
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(start, this.lineEnd);
- return false;
- }
-
- /*
- * Parse a method reference in @see tag
- */
- private Object parseMember(Object receiver) throws InvalidInputException {
- // Init
- this.identifierPtr = -1;
- this.identifierLengthPtr = -1;
- int start = this.scanner.getCurrentTokenStartPosition();
- this.memberStart = start;
-
- // Get member identifier
- if (readToken() == TerminalTokens.TokenNameIdentifier) {
- consumeToken();
- pushIdentifier(true);
- // Look for next token to know whether it's a field or method reference
- int previousPosition = this.index;
- if (readToken() == TerminalTokens.TokenNameLPAREN) {
- consumeToken();
- start = this.scanner.getCurrentTokenStartPosition();
- try {
- return parseArguments(receiver);
- } catch (InvalidInputException e) {
- int end = this.scanner.getCurrentTokenEndPosition() < this.lineEnd ?
- this.scanner.getCurrentTokenEndPosition() :
- this.scanner.getCurrentTokenStartPosition();
- end = end < this.lineEnd ? end : this.lineEnd;
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReferenceArgs(start, end);
- }
- return null;
- }
-
- // Reset position: we want to rescan last token
- this.index = previousPosition;
- this.scanner.currentPosition = previousPosition;
- this.currentTokenType = -1;
-
- // Verify character(s) after identifier (expecting space or end comment)
- if (!verifySpaceOrEndComment()) {
- int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
- if (this.source[end]=='\n') end--;
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end);
- return null;
- }
- return createFieldReference(receiver);
- }
- int end = getEndPosition() - 1;
- end = start > end ? start : end;
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, end);
- // Reset position: we want to rescan last token
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
- return null;
- }
-
- /*
- * Parse @param tag declaration
- */
- protected boolean parseParam() {
-
- // Store current token state
- int start = this.tagSourceStart;
- int end = this.tagSourceEnd;
-
- try {
- // Push identifier next
- int token = readToken();
- switch (token) {
- case TerminalTokens.TokenNameIdentifier :
- consumeToken();
- return pushParamName();
- case TerminalTokens.TokenNameEOF :
- break;
- default :
- start = this.scanner.getCurrentTokenStartPosition();
- end = getEndPosition();
- if (end < start) start = this.tagSourceStart;
- break;
- }
- } catch (InvalidInputException e) {
- end = getEndPosition();
- }
-
- // Reset position to avoid missing tokens when new line was encountered
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
-
- // Report problem
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingParamName(start, end);
- return false;
- }
-
- /*
- * Parse a qualified name and built a type reference if the syntax is valid.
- */
- protected Object parseQualifiedName(boolean reset) throws InvalidInputException {
-
- // Reset identifier stack if requested
- if (reset) {
- this.identifierPtr = -1;
- this.identifierLengthPtr = -1;
- }
-
- // Scan tokens
- int primitiveToken = -1;
- nextToken : for (int iToken = 0; ; iToken++) {
- int token = readToken();
- switch (token) {
- case TerminalTokens.TokenNameIdentifier :
- if (((iToken % 2) > 0)) { // identifiers must be odd tokens
- break nextToken;
- }
- pushIdentifier(iToken == 0);
- consumeToken();
- break;
-
- case TerminalTokens.TokenNameDOT :
- if ((iToken % 2) == 0) { // dots must be even tokens
- throw new InvalidInputException();
- }
- consumeToken();
- break;
-
- case TerminalTokens.TokenNamevoid :
- case TerminalTokens.TokenNameboolean :
- case TerminalTokens.TokenNamebyte :
- case TerminalTokens.TokenNamechar :
- case TerminalTokens.TokenNamedouble :
- case TerminalTokens.TokenNamefloat :
- case TerminalTokens.TokenNameint :
- case TerminalTokens.TokenNamelong :
- case TerminalTokens.TokenNameshort :
- if (iToken > 0) {
- throw new InvalidInputException();
- }
- pushIdentifier(true);
- primitiveToken = token;
- consumeToken();
- break nextToken;
-
- default :
- if (iToken == 0) {
- return null;
- }
- if ((iToken % 2) == 0) { // cannot leave on a dot
- // Reset position: we want to rescan last token
- if (this.kind == DOM_PARSER && this.currentTokenType != -1) {
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
- }
- throw new InvalidInputException();
- }
- break nextToken;
- }
- }
- // Reset position: we want to rescan last token
- if (this.currentTokenType != -1) {
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
- }
- this.lastIdentifierEndPosition = (int) this.identifierPositionStack[this.identifierPtr];
- return createTypeReference(primitiveToken);
- }
-
- /*
- * Parse a reference in @see tag
- */
- protected boolean parseReference(boolean plain) throws InvalidInputException {
- Object typeRef = null;
- Object reference = null;
- int previousPosition = -1;
- int typeRefStartPosition = -1;
- nextToken : while (this.index < this.scanner.eofPosition) {
- previousPosition = this.index;
- int token = readToken();
- switch (token) {
- case TerminalTokens.TokenNameStringLiteral : // @see "string"
- int start = this.scanner.getCurrentTokenStartPosition();
- consumeToken();
- // If typeRef != null we may raise a warning here to let user know there's an unused reference...
- // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
- if (typeRef != null) {
- start = this.tagSourceEnd+1;
- previousPosition = start;
- typeRef = null;
- }
- // verify end line (expecting empty or end comment)
- if (verifyEndLine(previousPosition)) {
- return true;
- }
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, this.lineEnd);
- return false;
- case TerminalTokens.TokenNameLESS : // @see "<a href="URL#Value">label</a>
- consumeToken();
- start = this.scanner.getCurrentTokenStartPosition();
- if (parseHref()) {
- consumeToken();
- // If typeRef != null we may raise a warning here to let user know there's an unused reference...
- // Currently as javadoc 1.4.2 ignore it, we do the same (see bug 69302)
- if (typeRef != null) {
- start = this.tagSourceEnd+1;
- previousPosition = start;
- typeRef = null;
- }
- // verify end line (expecting empty or end comment)
- if (verifyEndLine(previousPosition)) {
- return true;
- }
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, this.lineEnd);
- }
- return false;
- case TerminalTokens.TokenNameERROR :
- if (this.scanner.currentCharacter == '#') { // @see ...#member
- consumeToken();
- reference = parseMember(typeRef);
- if (reference != null) {
- return pushSeeRef(reference, plain);
- }
- return false;
- }
- break nextToken;
- case TerminalTokens.TokenNameIdentifier :
- if (typeRef == null) {
- typeRefStartPosition = this.scanner.getCurrentTokenStartPosition();
- typeRef = parseQualifiedName(true);
- break;
- }
- break nextToken;
- default :
- break nextToken;
- }
- }
-
- // Verify that we got a reference
- if (reference == null) reference = typeRef;
- if (reference == null) {
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingSeeReference(this.tagSourceStart, this.tagSourceEnd);
- return false;
- }
-
- // Reset position at the end of type reference
- this.index = this.lastIdentifierEndPosition+1;
- this.scanner.currentPosition = this.index;
- this.currentTokenType = -1;
-
- // Verify that line end does not start with an open parenthese (which could be a constructor reference wrongly written...)
- // See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=47215
- char ch = peekChar();
- if (ch == '(') {
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(typeRefStartPosition, this.lineEnd);
- return false;
- }
-
- // Verify that we get white space after reference
- if (!verifySpaceOrEndComment()) {
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
- int end = this.starPosition == -1 ? this.lineEnd : this.starPosition;
- if (this.source[end]=='\n') end--;
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMalformedSeeReference(typeRefStartPosition, end);
- return false;
- }
-
- // Everything is OK, store reference
- return pushSeeRef(reference, plain);
- }
-
- /*
- * Parse @return tag declaration
- */
- protected abstract boolean parseReturn();
-
- /*
- * Parse @see tag declaration
- */
- protected boolean parseSee(boolean plain) {
- int start = this.scanner.currentPosition;
- try {
- return parseReference(plain);
- } catch (InvalidInputException ex) {
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidSeeReference(start, getEndPosition());
- }
- // Reset position to avoid missing tokens when new line was encountered
- this.index = this.tokenPreviousPosition;
- this.scanner.currentPosition = this.tokenPreviousPosition;
- this.currentTokenType = -1;
- return false;
- }
-
- /*
- * Parse @return tag declaration
- */
- protected abstract boolean parseTag();
-
- /*
- * Parse @throws tag declaration
- */
- protected boolean parseThrows(boolean real) {
- int start = this.scanner.currentPosition;
- try {
- Object typeRef = parseQualifiedName(true);
- if (typeRef == null) {
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocMissingThrowsClassName(this.tagSourceStart, this.tagSourceEnd);
- } else {
- return pushThrowName(typeRef, real);
- }
- } catch (InvalidInputException ex) {
- if (this.sourceParser != null) this.sourceParser.problemReporter().javadocInvalidThrowsClass(start, getEndPosition());
- }
- return false;
- }
-
- /*
- * Return current character without move index position.
- */
- private char peekChar() {
- int idx = this.index;
- char c = this.source[idx++];
- if (c == '\\' && this.source[idx] == 'u') {
- int c1, c2, c3, c4;
- idx++;
- while (this.source[idx] == 'u')
- idx++;
- if (!(((c1 = Character.getNumericValue(this.source[idx++])) > 15 || c1 < 0)
- || ((c2 = Character.getNumericValue(this.source[idx++])) > 15 || c2 < 0)
- || ((c3 = Character.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[idx++])) > 15 || c4 < 0))) {
- c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
- }
- }
- return c;
- }
-
- /*
- * push the consumeToken on the identifier stack. Increase the total number of identifier in the stack.
- */
- protected void pushIdentifier(boolean newLength) {
-
- int stackLength = this.identifierStack.length;
- if (++this.identifierPtr >= stackLength) {
- System.arraycopy(
- this.identifierStack, 0,
- this.identifierStack = new char[stackLength + 10][], 0,
- stackLength);
- System.arraycopy(
- this.identifierPositionStack, 0,
- this.identifierPositionStack = new long[stackLength + 10], 0,
- stackLength);
- }
- this.identifierStack[this.identifierPtr] = this.scanner.getCurrentIdentifierSource();
- this.identifierPositionStack[this.identifierPtr] = (((long) this.scanner.startPosition) << 32) + (this.scanner.currentPosition - 1);
-
- if (newLength) {
- stackLength = this.identifierLengthStack.length;
- if (++this.identifierLengthPtr >= stackLength) {
- System.arraycopy(
- this.identifierLengthStack, 0,
- this.identifierLengthStack = new int[stackLength + 10], 0,
- stackLength);
- }
- this.identifierLengthStack[this.identifierLengthPtr] = 1;
- } else {
- this.identifierLengthStack[this.identifierLengthPtr]++;
- }
- }
-
- /*
- * Add a new obj on top of the ast stack.
- * If new length is required, then add also a new length in length stack.
- */
- protected void pushOnAstStack(Object node, boolean newLength) {
-
- if (node == null) {
- this.astLengthStack[++this.astLengthPtr] = 0;
- return;
- }
-
- int stackLength = this.astStack.length;
- if (++this.astPtr >= stackLength) {
- System.arraycopy(
- this.astStack, 0,
- this.astStack = new Object[stackLength + AstStackIncrement], 0,
- stackLength);
- this.astPtr = stackLength;
- }
- this.astStack[this.astPtr] = node;
-
- if (newLength) {
- stackLength = this.astLengthStack.length;
- if (++this.astLengthPtr >= stackLength) {
- System.arraycopy(
- this.astLengthStack, 0,
- this.astLengthStack = new int[stackLength + AstStackIncrement], 0,
- stackLength);
- }
- this.astLengthStack[this.astLengthPtr] = 1;
- } else {
- this.astLengthStack[this.astLengthPtr]++;
- }
- }
-
- /*
- * Push a param name in ast node stack.
- */
- protected abstract boolean pushParamName();
-
- /*
- * Push a reference statement in ast node stack.
- */
- protected abstract boolean pushSeeRef(Object statement, boolean plain);
-
- /*
- * Push a text element in ast node stack
- */
- protected abstract void pushText(int start, int end);
-
- /*
- * Push a throws type ref in ast node stack.
- */
- protected abstract boolean pushThrowName(Object typeRef, boolean real);
-
- /*
- * Read current character and move index position.
- * Warning: scanner position is unchanged using this method!
- */
- protected char readChar() {
-
- char c = this.source[this.index++];
- if (c == '\\' && 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 = Character.getNumericValue(this.source[this.index++])) > 15 || c1 < 0)
- || ((c2 = Character.getNumericValue(this.source[this.index++])) > 15 || c2 < 0)
- || ((c3 = Character.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4 = Character.getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) {
- c = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
- } else {
- // TODO (frederic) currently reset to previous position, perhaps signal a syntax error would be more appropriate
- this.index = pos;
- }
- }
- return c;
- }
-
- /*
- * Read token only if previous was consumed
- */
- private int readToken() throws InvalidInputException {
- if (this.currentTokenType < 0) {
- this.tokenPreviousPosition = this.scanner.currentPosition;
- this.currentTokenType = this.scanner.getNextToken();
- if (this.scanner.currentPosition > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
- this.lineStarted = false;
- while (this.currentTokenType == TerminalTokens.TokenNameMULTIPLY) {
- this.currentTokenType = this.scanner.getNextToken();
- }
- }
- this.index = this.scanner.currentPosition;
- this.lineStarted = true; // after having read a token, line is obviously started...
- }
- return this.currentTokenType;
- }
-
- private int readTokenAndConsume() throws InvalidInputException {
- int token = readToken();
- consumeToken();
- return token;
- }
-
- /*
- * Refresh start position and length of an inline tag.
- */
- protected void refreshInlineTagPosition(int previousPosition) {
- // do nothing by default
- }
-
- public String toString() {
- StringBuffer buffer = new StringBuffer();
- int startPos = this.scanner.currentPosition<this.index ? this.scanner.currentPosition : this.index;
- int endPos = this.scanner.currentPosition<this.index ? this.index : this.scanner.currentPosition;
- if (startPos == this.source.length)
- return "EOF\n\n" + new String(this.source); //$NON-NLS-1$
- if (endPos > this.source.length)
- return "behind the EOF\n\n" + new String(this.source); //$NON-NLS-1$
-
- char front[] = new char[startPos];
- System.arraycopy(this.source, 0, front, 0, startPos);
-
- int middleLength = (endPos - 1) - startPos + 1;
- char middle[];
- if (middleLength > -1) {
- middle = new char[middleLength];
- System.arraycopy(
- this.source,
- startPos,
- middle,
- 0,
- middleLength);
- } else {
- middle = CharOperation.NO_CHAR;
- }
-
- char end[] = new char[this.source.length - (endPos - 1)];
- System.arraycopy(
- this.source,
- (endPos - 1) + 1,
- end,
- 0,
- this.source.length - (endPos - 1) - 1);
-
- buffer.append(front);
- if (this.scanner.currentPosition<this.index) {
- buffer.append("\n===============================\nScanner current position here -->"); //$NON-NLS-1$
- } else {
- buffer.append("\n===============================\nParser index here -->"); //$NON-NLS-1$
- }
- buffer.append(middle);
- if (this.scanner.currentPosition<this.index) {
- buffer.append("<-- Parser index here\n===============================\n"); //$NON-NLS-1$
- } else {
- buffer.append("<-- Scanner current position here\n===============================\n"); //$NON-NLS-1$
- }
- buffer.append(end);
-
- return buffer.toString();
- }
-
- /*
- * Update
- */
- protected abstract void updateDocComment();
-
- /*
- * Update line end
- */
- protected void updateLineEnd() {
- while (this.index > (this.lineEnd+1)) { // be sure to be on next line (lineEnd is still on the same line)
- if (this.linePtr < this.lastLinePtr) {
- this.lineEnd = this.scanner.getLineEnd(++this.linePtr) - 1;
- } else {
- this.lineEnd = this.endComment;
- return;
- }
- }
- }
-
- /*
- * Verify that end of the line only contains space characters or end of comment.
- * Note that end of comment may be preceeding by several contiguous '*' chars.
- */
- private boolean verifyEndLine(int textPosition) {
- int startPosition = this.index;
- int previousPosition = this.index;
- this.starPosition = -1;
- char ch = readChar();
- nextChar: while (true) {
- switch (ch) {
- case '\r':
- case '\n':
- if (this.kind == DOM_PARSER) {
- parseTag();
- pushText(textPosition, previousPosition);
- }
- this.index = previousPosition;
- return true;
- case '\u000c' : /* FORM FEED */
- case ' ' : /* SPACE */
- case '\t' : /* HORIZONTAL TABULATION */
- if (this.starPosition >= 0) break nextChar;
- break;
- case '*':
- this.starPosition = previousPosition;
- break;
- case '/':
- if (this.starPosition >= textPosition) {
- if (this.kind == DOM_PARSER) {
- parseTag();
- pushText(textPosition, this.starPosition);
- }
- return true;
- }
- default :
- // leave loop
- break nextChar;
-
- }
- previousPosition = this.index;
- ch = readChar();
- }
- this.index = startPosition;
- return false;
- }
-
- /*
- * Verify that some text exists after a @return tag. Text must be different than
- * end of comment which may be preceeding by several '*' chars.
- */
- private boolean verifyCharsAfterReturnTag(int startPosition) {
- // Whitespace or inline tag closing brace
- int previousPosition = this.index;
- char ch = readChar();
- boolean malformed = true;
- while (Character.isWhitespace(ch)) {
- malformed = false;
- previousPosition = this.index;
- ch = readChar();
- }
- // End of comment
- this.starPosition = -1;
- nextChar: while (this.index<this.source.length) {
- switch (ch) {
- case '*':
- // valid whatever the number of star before last '/'
- this.starPosition = previousPosition;
- break;
- case '/':
- if (this.starPosition >= startPosition) { // valid only if a star was previous character
- return false;
- }
- default :
- // valid if any other character is encountered, even white spaces
- this.index = startPosition;
- return !malformed;
-
- }
- previousPosition = this.index;
- ch = readChar();
- }
- this.index = startPosition;
- return false;
- }
-
- /*
- * Verify characters after a name matches one of following conditions:
- * 1- first character is a white space
- * 2- first character is a closing brace *and* we're currently parsing an inline tag
- * 3- are the end of comment (several contiguous star ('*') characters may be
- * found before the last slash ('/') character).
- */
- private boolean verifySpaceOrEndComment() {
- int startPosition = this.index;
- // Whitespace or inline tag closing brace
- char ch = peekChar();
- switch (ch) {
- case '}':
- return this.inlineTagStarted;
- default:
- if (Character.isWhitespace(ch)) {
- return true;
- }
- }
- // End of comment
- int previousPosition = this.index;
- this.starPosition = -1;
- ch = readChar();
- nextChar: while (this.index<this.source.length) {
- switch (ch) {
- case '*':
- // valid whatever the number of star before last '/'
- this.starPosition = previousPosition;
- break;
- case '/':
- if (this.starPosition >= startPosition) { // valid only if a star was previous character
- return true;
- }
- default :
- // invalid whatever other character, even white spaces
- this.index = startPosition;
- return false;
-
- }
- previousPosition = this.index;
- ch = readChar();
- }
- this.index = startPosition;
- return false;
- }
-}