/******************************************************************************* * Copyright (c) 2000, 2010 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 *******************************************************************************/ 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; import org.eclipse.jdt.internal.compiler.util.Util; /** * Parser specialized for decoding javadoc comments */ public abstract class AbstractCommentParser implements JavadocTagConstants { // Kind of comment parser public final static int COMPIL_PARSER= 0x0001; public final static int DOM_PARSER= 0x0002; public final static int SELECTION_PARSER= 0x0004; public final static int COMPLETION_PARSER= 0x0008; public final static int SOURCE_PARSER= 0x0010; public final static int FORMATTER_COMMENT_PARSER= 0x0020; protected final static int PARSER_KIND= 0x00FF; protected final static int TEXT_PARSE= 0x0100; // flag saying that text must be stored protected final static int TEXT_VERIF= 0x0200; // flag saying that text must be verified // Parser recovery states protected final static int QUALIFIED_NAME_RECOVERY= 1; protected final static int ARGUMENT_RECOVERY= 2; protected final static int ARGUMENT_TYPE_RECOVERY= 3; protected final static int EMPTY_ARGUMENT_RECOVERY= 4; // Parse infos public Scanner scanner; public char[] source; protected Parser sourceParser; private int currentTokenType= -1; // Options public boolean checkDocComment= false; public boolean reportProblems; protected long complianceLevel; protected long sourceLevel; // Support for {@inheritDoc} protected long[] inheritedPositions; protected int inheritedPositionsPtr; private final static int INHERITED_POSITIONS_ARRAY_INCREMENT= 4; // Results protected boolean deprecated; protected Object returnStatement; // Positions protected int javadocStart, javadocEnd; protected int javadocTextStart, javadocTextEnd= -1; protected int firstTagPosition; protected int index, lineEnd; protected int tokenPreviousPosition, lastIdentifierEndPosition, starPosition; protected int textStart, memberStart; protected int tagSourceStart, tagSourceEnd; protected int inlineTagStart; protected int[] lineEnds; // Flags protected boolean lineStarted= false; protected boolean inlineTagStarted= false; protected boolean abort= false; protected int kind; protected int tagValue= NO_TAG_VALUE; protected int lastBlockTagValue= NO_TAG_VALUE; // 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 final static int AST_STACK_INCREMENT= 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]; this.reportProblems= sourceParser != null; if (sourceParser != null) { this.checkDocComment= this.sourceParser.options.docCommentSupport; this.sourceLevel= this.sourceParser.options.sourceLevel; this.scanner.sourceLevel= this.sourceLevel; this.complianceLevel= this.sourceParser.options.complianceLevel; } } /* (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 commentParse() { boolean validComment= true; try { // Init local variables this.astLengthPtr= -1; this.astPtr= -1; this.identifierPtr= -1; this.currentTokenType= -1; setInlineTagStarted(false); this.inlineTagStart= -1; this.lineStarted= false; this.returnStatement= null; this.inheritedPositions= null; this.lastBlockTagValue= NO_TAG_VALUE; this.deprecated= false; this.lastLinePtr= getLineNumber(this.javadocEnd); this.textStart= -1; this.abort= false; char previousChar= 0; int invalidTagLineEnd= -1; int invalidInlineTagLineEnd= -1; boolean lineHasStar= true; boolean verifText= (this.kind & TEXT_VERIF) != 0; boolean isDomParser= (this.kind & DOM_PARSER) != 0; boolean isFormatterParser= (this.kind & FORMATTER_COMMENT_PARSER) != 0; int lastStarPosition= -1; // Init scanner position this.linePtr= getLineNumber(this.firstTagPosition); int realStart= this.linePtr == 1 ? this.javadocStart : this.scanner.getLineEnd(this.linePtr - 1) + 1; if (realStart < this.javadocStart) realStart= this.javadocStart; this.scanner.resetTo(realStart, this.javadocEnd); this.index= realStart; if (realStart == this.javadocStart) { readChar(); // starting '/' readChar(); // first '*' } int previousPosition= this.index; char nextCharacter= 0; if (realStart == this.javadocStart) { nextCharacter= readChar(); // second '*' while (peekChar() == '*') { nextCharacter= readChar(); // read all contiguous '*' } this.javadocTextStart= this.index; } this.lineEnd= (this.linePtr == this.lastLinePtr) ? this.javadocEnd : this.scanner.getLineEnd(this.linePtr) - 1; this.javadocTextEnd= this.javadocEnd - 2; // supposed text end, it will be refined later... // Loop on each comment character int textEndPosition= -1; while (!this.abort && this.index < this.javadocEnd) { // Store previous position and char 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(); } // Consume rules depending on the read character switch (nextCharacter) { case '@': // Start tag parsing only if we are on line beginning or at inline tag beginning if ((!this.lineStarted || previousChar == '{')) { if (this.inlineTagStarted) { setInlineTagStarted(false); // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279 // Cannot have @ inside inline comment if (this.reportProblems) { int end= previousPosition < invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd; this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end); } validComment= false; if (this.textStart != -1 && this.textStart < textEndPosition) { pushText(this.textStart, textEndPosition); } if (isDomParser || isFormatterParser) { refreshInlineTagPosition(textEndPosition); } } if (previousChar == '{') { if (this.textStart != -1) { if (this.textStart < textEndPosition) { pushText(this.textStart, textEndPosition); } } setInlineTagStarted(true); invalidInlineTagLineEnd= this.lineEnd; } else if (this.textStart != -1 && this.textStart < invalidTagLineEnd) { pushText(this.textStart, invalidTagLineEnd); } this.scanner.resetTo(this.index, this.javadocEnd); this.currentTokenType= -1; // flush token cache at line begin try { if (!parseTag(previousPosition)) { // 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 (isDomParser) { createTag(); } this.textStart= this.tagSourceEnd + 1; invalidTagLineEnd= this.lineEnd; textEndPosition= this.index; } } catch (InvalidInputException e) { consumeToken(); } } else { textEndPosition= this.index; if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) { refreshReturnStatement(); } else if (isFormatterParser) { if (this.textStart == -1) this.textStart= previousPosition; } } this.lineStarted= true; break; case '\r': case '\n': if (this.lineStarted) { if (isFormatterParser && !ScannerHelper.isWhitespace(previousChar)) { textEndPosition= previousPosition; } if (this.textStart != -1 && this.textStart < textEndPosition) { pushText(this.textStart, textEndPosition); } } this.lineStarted= false; lineHasStar= false; // Fix bug 51650 this.textStart= -1; break; case '}': if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) { refreshReturnStatement(); } if (this.inlineTagStarted) { if (this.lineStarted && this.textStart != -1 && this.textStart < textEndPosition) { pushText(this.textStart, textEndPosition); } refreshInlineTagPosition(previousPosition); if (!isFormatterParser) this.textStart= this.index; setInlineTagStarted(false); } else { if (!this.lineStarted) { this.textStart= previousPosition; } } this.lineStarted= true; textEndPosition= this.index; break; case '{': if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) { refreshReturnStatement(); } if (this.inlineTagStarted) { setInlineTagStarted(false); // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279 // Cannot have opening brace in inline comment if (this.reportProblems) { int end= previousPosition < invalidInlineTagLineEnd ? previousPosition : invalidInlineTagLineEnd; this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end); } if (this.lineStarted && this.textStart != -1 && this.textStart < textEndPosition) { pushText(this.textStart, textEndPosition); } refreshInlineTagPosition(textEndPosition); textEndPosition= this.index; } else if (peekChar() != '@') { if (this.textStart == -1) this.textStart= previousPosition; textEndPosition= this.index; } if (!this.lineStarted) { this.textStart= previousPosition; } this.lineStarted= true; this.inlineTagStart= previousPosition; break; case '*': // Store the star position as text start while formatting lastStarPosition= previousPosition; if (previousChar != '*') { this.starPosition= previousPosition; if (isDomParser || isFormatterParser) { if (lineHasStar) { this.lineStarted= true; if (this.textStart == -1) { this.textStart= previousPosition; if (this.index <= this.javadocTextEnd) textEndPosition= this.index; } } if (!this.lineStarted) { lineHasStar= true; } } } break; case '\u000c': /* FORM FEED */ case ' ': /* SPACE */ case '\t': /* HORIZONTAL TABULATION */ // Do not include trailing spaces in text while formatting if (isFormatterParser) { if (!ScannerHelper.isWhitespace(previousChar)) { textEndPosition= previousPosition; } } else if (this.lineStarted && isDomParser) { textEndPosition= this.index; } break; case '/': if (previousChar == '*') { // End of javadoc break; } // $FALL-THROUGH$ - fall through default case default: if (isFormatterParser && nextCharacter == '<') { // html tags are meaningful for formatter parser int initialIndex= this.index; this.scanner.resetTo(this.index, this.javadocEnd); if (!ScannerHelper.isWhitespace(previousChar)) { textEndPosition= previousPosition; } if (parseHtmlTag(previousPosition, textEndPosition)) { break; } if (this.abort) return false; // Wrong html syntax continue to process character normally this.scanner.currentPosition= initialIndex; this.index= initialIndex; } if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) { refreshReturnStatement(); } if (!this.lineStarted || this.textStart == -1) { this.textStart= previousPosition; } this.lineStarted= true; textEndPosition= this.index; break; } } this.javadocTextEnd= this.starPosition - 1; // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279 // Cannot leave comment inside inline comment if (this.inlineTagStarted) { if (this.reportProblems) { int end= this.javadocTextEnd < invalidInlineTagLineEnd ? this.javadocTextEnd : invalidInlineTagLineEnd; if (this.index >= this.javadocEnd) end= invalidInlineTagLineEnd; this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, end); } if (this.lineStarted && this.textStart != -1 && this.textStart < textEndPosition) { pushText(this.textStart, textEndPosition); } refreshInlineTagPosition(textEndPosition); setInlineTagStarted(false); } else if (this.lineStarted && this.textStart != -1 && this.textStart <= textEndPosition && (this.textStart < this.starPosition || this.starPosition == lastStarPosition)) { pushText(this.textStart, textEndPosition); } updateDocComment(); } catch (Exception ex) { validComment= false; } return validComment; } protected void consumeToken() { this.currentTokenType= -1; // flush token cache updateLineEnd(); } protected abstract Object createArgumentReference(char[] name, int dim, boolean isVarargs, Object typeRef, long[] dimPos, long argNamePos) throws InvalidInputException; protected boolean createFakeReference(int start) { // Do nothing by default return true; } 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 void createTag(); protected abstract Object createTypeReference(int primitiveToken); private int getIndexPosition() { if (this.index > this.lineEnd) { return this.lineEnd; } else { return this.index - 1; } } /** * 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 Util.getLineNumber(position, this.scanner.lineEnds, 0, this.scanner.linePtr); } if (this.lineEnds == null) return 1; return Util.getLineNumber(position, this.lineEnds, 0, this.lineEnds.length - 1); } private int getTokenEndPosition() { if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) { return this.lineEnd; } else { return this.scanner.getCurrentTokenEndPosition(); } } /** * @return Returns the currentTokenType. */ protected int getCurrentTokenType() { return this.currentTokenType; } /* * Parse argument in @see tag method reference */ protected 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(); Object typeRef= null; int dim= 0; boolean isVarargs= false; long[] dimPositions= new long[20]; // assume that there won't be more than 20 dimensions... char[] name= null; long argNamePos= -1; // Parse arguments declaration if method reference nextArg: while (this.index < this.scanner.eofPosition) { // Read argument type reference try { typeRef= parseQualifiedName(false); if (this.abort) return null; // May be aborted by specialized parser } 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.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end); return null; } this.lineStarted= true; return createMethodReference(receiver, null); } break nextArg; } iToken++; // Read possible additional type info dim= 0; isVarargs= false; if (readToken() == TerminalTokens.TokenNameLBRACKET) { // array declaration 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(); } } else if (readToken() == TerminalTokens.TokenNameELLIPSIS) { // ellipsis declaration int dimStart= this.scanner.getCurrentTokenStartPosition(); dimPositions[dim++]= (((long)dimStart) << 32) + this.scanner.getCurrentTokenEndPosition(); consumeToken(); isVarargs= true; } // Read argument name 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(); name= argName == null ? CharOperation.NO_CHAR : argName; if (token == TerminalTokens.TokenNameCOMMA) { // Create new argument Object argument= createArgumentReference(name, dim, isVarargs, typeRef, dimPositions, argNamePos); if (this.abort) return null; // May be aborted by specialized parser 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.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end); return null; } // Create new argument Object argument= createArgumentReference(name, dim, isVarargs, typeRef, dimPositions, argNamePos); if (this.abort) return null; // May be aborted by specialized parser arguments.add(argument); consumeToken(); return createMethodReference(receiver, arguments); } else { break nextArg; } } // Something wrong happened => Invalid input throw new InvalidInputException(); } /** * Parse a possible HTML tag like: * <ul> * <li><code> * <li><br> * <li><h?> * </ul> * * Note that the default is to do nothing! * * @param previousPosition The position of the '<' character on which the tag might start * @param endTextPosition The position of the end of the previous text * @return <code>true</code> if a valid html tag has been parsed, <code>false</code> * otherwise * @throws InvalidInputException If any problem happens during the parse in this area */ protected boolean parseHtmlTag(int previousPosition, int endTextPosition) throws InvalidInputException { return false; } /* * Parse an URL link reference in @see tag */ protected boolean parseHref() throws InvalidInputException { boolean skipComments= this.scanner.skipComments; this.scanner.skipComments= true; try { int start= this.scanner.getCurrentTokenStartPosition(); char currentChar= readChar(); if (currentChar == 'a' || currentChar == 'A') { this.scanner.currentPosition= this.index; if (readToken() == TerminalTokens.TokenNameIdentifier) { consumeToken(); try { if (CharOperation.equals(this.scanner.getCurrentIdentifierSource(), HREF_TAG, false) && readToken() == TerminalTokens.TokenNameEQUAL) { consumeToken(); if (readToken() == TerminalTokens.TokenNameStringLiteral) { consumeToken(); while (this.index < this.javadocEnd) { // main loop to search for the </a> pattern // Skip all characters after string literal until closing '>' (see bug 68726) while (readToken() != TerminalTokens.TokenNameGREATER) { if (this.scanner.currentPosition >= this.scanner.eofPosition || this.scanner.currentCharacter == '@' || (this.inlineTagStarted && 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.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after... if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeHref(start, this.lineEnd); } return false; } this.currentTokenType= -1; // consume token without updating line end } 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 == '@' || (this.inlineTagStarted && 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.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after... if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeHref(start, this.lineEnd); } return false; } consumeToken(); } consumeToken(); start= this.scanner.getCurrentTokenStartPosition(); currentChar= readChar(); // search for the </a> pattern and store last char read if (currentChar == '/') { currentChar= readChar(); if (currentChar == 'a' || currentChar == 'A') { currentChar= readChar(); if (currentChar == '>') { return true; // valid href } } } // search for invalid char in tags if (currentChar == '\r' || currentChar == '\n' || currentChar == '\t' || currentChar == ' ') { break; } } } } } 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.tagValue != TAG_VALUE_VALUE) { // do not report error for @value tag, this will be done after... if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidSeeHref(start, this.lineEnd); } } finally { this.scanner.skipComments= skipComments; } return false; } /* * Parse tag followed by an identifier */ protected boolean parseIdentifierTag(boolean report) { int token= readTokenSafely(); switch (token) { case TerminalTokens.TokenNameIdentifier: pushIdentifier(true, false); return true; } if (report) { this.sourceParser.problemReporter().javadocMissingIdentifier(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers); } return false; } /* * Parse a method reference in @see tag */ protected 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) { if (this.scanner.currentCharacter == '.') { // member name may be qualified (inner class constructor reference) parseQualifiedName(true); } else { consumeToken(); pushIdentifier(true, false); } // 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.reportProblems) 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.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(start, end); return null; } return createFieldReference(receiver); } int end= getTokenEndPosition() - 1; end= start > end ? start : end; if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(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() throws InvalidInputException { // Store current state int start= this.tagSourceStart; int end= this.tagSourceEnd; boolean tokenWhiteSpace= this.scanner.tokenizeWhiteSpace; this.scanner.tokenizeWhiteSpace= true; try { // Verify that there are whitespaces after tag boolean isCompletionParser= (this.kind & COMPLETION_PARSER) != 0; if (this.scanner.currentCharacter != ' ' && !ScannerHelper.isWhitespace(this.scanner.currentCharacter)) { if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(start, this.scanner.getCurrentTokenEndPosition()); if (!isCompletionParser) { this.scanner.currentPosition= start; this.index= start; } this.currentTokenType= -1; return false; } // Get first non whitespace token this.identifierPtr= -1; this.identifierLengthPtr= -1; boolean hasMultiLines= this.scanner.currentPosition > (this.lineEnd + 1); boolean isTypeParam= false; boolean valid= true, empty= true; boolean mayBeGeneric= this.sourceLevel >= ClassFileConstants.JDK1_5; 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$ - fall through next case to report error case TerminalTokens.TokenNameLESS: if (valid && mayBeGeneric) { // store '<' in identifiers stack as we need to add it to tag element (bug 79809) pushIdentifier(true, true); start= this.scanner.getCurrentTokenStartPosition(); end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition(); isTypeParam= true; break nextToken; } // $FALL-THROUGH$ - fall through next case to report error default: if (token == TerminalTokens.TokenNameLEFT_SHIFT) isTypeParam= true; if (valid && !hasMultiLines) start= this.scanner.getCurrentTokenStartPosition(); valid= false; if (!hasMultiLines) { empty= false; end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition(); break; } end= this.lineEnd; // $FALL-THROUGH$ - 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; // $FALL-THROUGH$ - 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 if (mayBeGeneric && isTypeParam) this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end); else this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end); if (!isCompletionParser) { this.scanner.currentPosition= start; this.index= start; } this.currentTokenType= -1; return false; } } // Scan more tokens for type parameter declaration if (isTypeParam && mayBeGeneric) { // Get type parameter name nextToken: while (true) { this.currentTokenType= -1; try { token= readToken(); } catch (InvalidInputException e) { valid= false; } switch (token) { case TerminalTokens.TokenNameWHITESPACE: if (valid && this.scanner.currentPosition <= (this.lineEnd + 1)) { break; } // $FALL-THROUGH$ - if not valid fall through next case to report error case TerminalTokens.TokenNameEOF: if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end); if (!isCompletionParser) { this.scanner.currentPosition= start; this.index= start; } this.currentTokenType= -1; return false; case TerminalTokens.TokenNameIdentifier: end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition(); if (valid) { // store param name id pushIdentifier(false, false); break nextToken; } break; default: end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition(); valid= false; break; } } // Get last character of type parameter declaration boolean spaces= false; nextToken: while (true) { this.currentTokenType= -1; try { token= readToken(); } catch (InvalidInputException e) { valid= false; } switch (token) { case TerminalTokens.TokenNameWHITESPACE: if (this.scanner.currentPosition > (this.lineEnd + 1)) { // do not accept type parameter declaration on several lines hasMultiLines= true; valid= false; } spaces= true; if (valid) break; // $FALL-THROUGH$ - if not valid fall through next case to report error case TerminalTokens.TokenNameEOF: if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end); if (!isCompletionParser) { this.scanner.currentPosition= start; this.index= start; } this.currentTokenType= -1; return false; case TerminalTokens.TokenNameGREATER: end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition(); if (valid) { // store '>' in identifiers stack as we need to add it to tag element (bug 79809) pushIdentifier(false, true); break nextToken; } break; default: if (!spaces) end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition(); valid= false; break; } } } // Verify that tag name is well followed by white spaces if (valid) { this.currentTokenType= -1; int restart= this.scanner.currentPosition; try { token= readTokenAndConsume(); } catch (InvalidInputException e) { valid= false; } if (token == TerminalTokens.TokenNameWHITESPACE) { this.scanner.resetTo(restart, this.javadocEnd); this.index= restart; return pushParamName(isTypeParam); } } // Report problem this.currentTokenType= -1; if (isCompletionParser) return false; if (this.reportProblems) { // we only need end if we report problems end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition(); try { while ((token= readToken()) != TerminalTokens.TokenNameWHITESPACE && token != TerminalTokens.TokenNameEOF) { this.currentTokenType= -1; end= hasMultiLines ? this.lineEnd : this.scanner.getCurrentTokenEndPosition(); } } catch (InvalidInputException e) { end= this.lineEnd; } if (mayBeGeneric && isTypeParam) this.sourceParser.problemReporter().javadocInvalidParamTypeParameter(start, end); else this.sourceParser.problemReporter().javadocInvalidParamTagName(start, end); } this.scanner.currentPosition= start; this.index= start; this.currentTokenType= -1; return false; } finally { // we have to make sure that this is reset to the previous value even if an exception occurs this.scanner.tokenizeWhiteSpace= tokenWhiteSpace; } } /* * 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; int parserKind= this.kind & PARSER_KIND; nextToken: for (int iToken= 0;; iToken++) { int token= readTokenSafely(); switch (token) { case TerminalTokens.TokenNameIdentifier: if (((iToken & 1) != 0)) { // identifiers must be odd tokens break nextToken; } pushIdentifier(iToken == 0, false); consumeToken(); break; case TerminalTokens.TokenNameDOT: if ((iToken & 1) == 0) { // dots must be even tokens throw new InvalidInputException(); } consumeToken(); break; 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: if (iToken == 0) { pushIdentifier(true, true); primitiveToken= token; consumeToken(); break nextToken; } // Fall through default case to verify that we do not leave on a dot //$FALL-THROUGH$ default: if (iToken == 0) { if (this.identifierPtr >= 0) { this.lastIdentifierEndPosition= (int)this.identifierPositionStack[this.identifierPtr]; } return null; } if ((iToken & 1) == 0) { // cannot leave on a dot switch (parserKind) { case COMPLETION_PARSER: if (this.identifierPtr >= 0) { this.lastIdentifierEndPosition= (int)this.identifierPositionStack[this.identifierPtr]; } return syntaxRecoverQualifiedName(primitiveToken); case DOM_PARSER: if (this.currentTokenType != -1) { // Reset position: we want to rescan last token this.index= this.tokenPreviousPosition; this.scanner.currentPosition= this.tokenPreviousPosition; this.currentTokenType= -1; } // $FALL-THROUGH$ - fall through default case to raise exception default: throw new InvalidInputException(); } } break nextToken; } } // Reset position: we want to rescan last token if (parserKind != COMPLETION_PARSER && this.currentTokenType != -1) { this.index= this.tokenPreviousPosition; this.scanner.currentPosition= this.tokenPreviousPosition; this.currentTokenType= -1; } if (this.identifierPtr >= 0) { this.lastIdentifierEndPosition= (int)this.identifierPositionStack[this.identifierPtr]; } return createTypeReference(primitiveToken); } /* * Parse a reference in @see tag */ protected boolean parseReference() throws InvalidInputException { int currentPosition= this.scanner.currentPosition; try { Object typeRef= null; Object reference= null; int previousPosition= -1; int typeRefStartPosition= -1; // Get reference tokens nextToken: while (this.index < this.scanner.eofPosition) { previousPosition= this.index; int token= readTokenSafely(); switch (token) { case TerminalTokens.TokenNameStringLiteral: // @see "string" // 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) break nextToken; consumeToken(); int start= this.scanner.getCurrentTokenStartPosition(); if (this.tagValue == TAG_VALUE_VALUE) { // String reference are not allowed for @value tag if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getTokenEndPosition(), this.sourceParser.modifiers); return false; } // verify end line if (verifyEndLine(previousPosition)) { return createFakeReference(start); } if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedText(this.scanner.currentPosition, this.lineEnd); return false; case TerminalTokens.TokenNameLESS: // @see <a href="URL#Value">label</a> // 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) break nextToken; consumeToken(); start= this.scanner.getCurrentTokenStartPosition(); if (parseHref()) { consumeToken(); if (this.tagValue == TAG_VALUE_VALUE) { // String reference are not allowed for @value tag if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getIndexPosition(), this.sourceParser.modifiers); return false; } // verify end line if (verifyEndLine(previousPosition)) { return createFakeReference(start); } if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedText(this.scanner.currentPosition, this.lineEnd); } else if (this.tagValue == TAG_VALUE_VALUE) { if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidValueReference(start, getIndexPosition(), this.sourceParser.modifiers); } return false; case TerminalTokens.TokenNameERROR: consumeToken(); if (this.scanner.currentCharacter == '#') { // @see ...#member reference= parseMember(typeRef); if (reference != null) { return pushSeeRef(reference); } return false; } char[] currentError= this.scanner.getCurrentIdentifierSource(); if (currentError.length > 0 && currentError[0] == '"') { if (this.reportProblems) { boolean isUrlRef= false; if (this.tagValue == TAG_SEE_VALUE) { int length= currentError.length, i= 1 /* first char is " */; while (i < length && ScannerHelper.isLetter(currentError[i])) { i++; } if (i < (length - 2) && currentError[i] == ':' && currentError[i + 1] == '/' && currentError[i + 2] == '/') { isUrlRef= true; } } if (isUrlRef) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=207765 // handle invalid URL references in javadoc with dedicated message this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(this.scanner.getCurrentTokenStartPosition(), getTokenEndPosition()); } else { this.sourceParser.problemReporter().javadocInvalidReference(this.scanner.getCurrentTokenStartPosition(), getTokenEndPosition()); } } return false; } break nextToken; case TerminalTokens.TokenNameIdentifier: if (typeRef == null) { typeRefStartPosition= this.scanner.getCurrentTokenStartPosition(); typeRef= parseQualifiedName(true); if (this.abort) return false; // May be aborted by specialized parser 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.tagValue == TAG_VALUE_VALUE) { if ((this.kind & DOM_PARSER) != 0) createTag(); return true; } if (this.reportProblems) { this.sourceParser.problemReporter().javadocMissingReference(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers); } return false; } // Reset position at the end of type reference if (this.lastIdentifierEndPosition > this.javadocStart) { this.index= this.lastIdentifierEndPosition + 1; this.scanner.currentPosition= this.index; } this.currentTokenType= -1; // In case of @value, we have an invalid reference (only static field refs are valid for this tag) if (this.tagValue == TAG_VALUE_VALUE) { if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(typeRefStartPosition, this.lineEnd); return false; } int currentIndex= this.index; // store current index char ch= readChar(); switch (ch) { // 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 case '(': if (this.reportProblems) this.sourceParser.problemReporter().javadocMissingHashCharacter(typeRefStartPosition, this.lineEnd, String.valueOf(this.source, typeRefStartPosition, this.lineEnd - typeRefStartPosition + 1)); return false; // Search for the :// URL pattern // See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=168849 case ':': ch= readChar(); if (ch == '/' && ch == readChar()) { if (this.reportProblems) { this.sourceParser.problemReporter().javadocInvalidSeeUrlReference(typeRefStartPosition, this.lineEnd); return false; } } } // revert to last stored index this.index= currentIndex; // 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.reportProblems) this.sourceParser.problemReporter().javadocMalformedSeeReference(typeRefStartPosition, end); return false; } // Everything is OK, store reference return pushSeeRef(reference); } catch (InvalidInputException ex) { if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidReference(currentPosition, getTokenEndPosition()); } // 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 tag declaration */ protected abstract boolean parseTag(int previousPosition) throws InvalidInputException; /* * Parse @throws tag declaration */ protected boolean parseThrows() { int start= this.scanner.currentPosition; try { Object typeRef= parseQualifiedName(true); if (this.abort) return false; // May be aborted by specialized parser if (typeRef == null) { if (this.reportProblems) this.sourceParser.problemReporter().javadocMissingThrowsClassName(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers); } else { return pushThrowName(typeRef); } } catch (InvalidInputException ex) { if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidThrowsClass(start, getTokenEndPosition()); } return false; } /* * Return current character without move index position. */ protected 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= ScannerHelper.getNumericValue(this.source[idx++])) > 15 || c1 < 0) || ((c2= ScannerHelper.getNumericValue(this.source[idx++])) > 15 || c2 < 0) || ((c3= ScannerHelper.getNumericValue(this.source[idx++])) > 15 || c3 < 0) || ((c4= ScannerHelper.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, boolean isToken) { 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]= isToken ? this.scanner.getCurrentTokenSource() : 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) { int stackLength= this.astLengthStack.length; if (++this.astLengthPtr >= stackLength) { System.arraycopy( this.astLengthStack, 0, this.astLengthStack= new int[stackLength + AST_STACK_INCREMENT], 0, stackLength); } 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 + AST_STACK_INCREMENT], 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 + AST_STACK_INCREMENT], 0, stackLength); } this.astLengthStack[this.astLengthPtr]= 1; } else { this.astLengthStack[this.astLengthPtr]++; } } /* * Push a param name in ast node stack. */ protected abstract boolean pushParamName(boolean isTypeParam); /* * Push a reference statement in ast node stack. */ protected abstract boolean pushSeeRef(Object statement); /* * Push a text element in ast node stack */ protected void pushText(int start, int end) { // do not store text by default } /* * Push a throws type ref in ast node stack. */ protected abstract boolean pushThrowName(Object typeRef); /* * 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= 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))) { 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 */ protected 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; } protected int readTokenAndConsume() throws InvalidInputException { int token= readToken(); consumeToken(); return token; } /* * Read token without throwing any InvalidInputException exception. * Returns TerminalTokens.TokenNameERROR instead. */ protected int readTokenSafely() { int token= TerminalTokens.TokenNameERROR; try { token= readToken(); } catch (InvalidInputException iie) { // token is already set to error } return token; } protected void recordInheritedPosition(long position) { if (this.inheritedPositions == null) { this.inheritedPositions= new long[INHERITED_POSITIONS_ARRAY_INCREMENT]; this.inheritedPositionsPtr= 0; } else { if (this.inheritedPositionsPtr == this.inheritedPositions.length) { System.arraycopy( this.inheritedPositions, 0, this.inheritedPositions= new long[this.inheritedPositionsPtr + INHERITED_POSITIONS_ARRAY_INCREMENT], 0, this.inheritedPositionsPtr); } } this.inheritedPositions[this.inheritedPositionsPtr++]= position; } /* * Refresh start position and length of an inline tag. */ protected void refreshInlineTagPosition(int previousPosition) { // do nothing by default } /* * Refresh return statement */ protected void refreshReturnStatement() { // do nothing by default } /** * @param started the inlineTagStarted to set */ protected void setInlineTagStarted(boolean started) { this.inlineTagStarted= started; } /* * Entry point for recovery on invalid syntax */ protected Object syntaxRecoverQualifiedName(int primitiveToken) throws InvalidInputException { // do nothing, just an entry point for recovery return null; } 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.javadocEnd; return; } } } /* * Verify that end of the line only contains space characters or end of comment. * Note that end of comment may be preceding by several contiguous '*' chars. */ protected boolean verifyEndLine(int textPosition) { boolean domParser= (this.kind & DOM_PARSER) != 0; // Special case for inline tag if (this.inlineTagStarted) { // expecting closing brace if (peekChar() == '}') { if (domParser) { createTag(); pushText(textPosition, this.starPosition); } return true; } return false; } int startPosition= this.index; int previousPosition= this.index; this.starPosition= -1; char ch= readChar(); nextChar: while (true) { switch (ch) { case '\r': case '\n': if (domParser) { createTag(); 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) { // valid only if a star was the previous character if (domParser) { createTag(); pushText(textPosition, this.starPosition); } return true; } break nextChar; default: // leave loop break nextChar; } 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). */ protected boolean verifySpaceOrEndComment() { this.starPosition= -1; int startPosition= this.index; // Whitespace or inline tag closing brace char ch= peekChar(); switch (ch) { case '}': return this.inlineTagStarted; default: if (ScannerHelper.isWhitespace(ch)) { return true; } } // End of comment int previousPosition= this.index; ch= readChar(); 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 the previous character return true; } // $FALL-THROUGH$ - fall through to invalid case default: // invalid whatever other character, even white spaces this.index= startPosition; return false; } previousPosition= this.index; ch= readChar(); } this.index= startPosition; return false; } }