blob: 84db450a1e5abfdb0d88e214b90680235c2edd30 [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.lang.java.parser;
import com.intellij.lang.PsiBuilder;
import com.intellij.openapi.util.Key;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavadocParser {
private static final TokenSet TAG_VALUES_SET = TokenSet.create(
JavaDocTokenType.DOC_TAG_VALUE_TOKEN, JavaDocTokenType.DOC_TAG_VALUE_COMMA, JavaDocTokenType.DOC_TAG_VALUE_DOT,
JavaDocTokenType.DOC_TAG_VALUE_LPAREN, JavaDocTokenType.DOC_TAG_VALUE_RPAREN, JavaDocTokenType.DOC_TAG_VALUE_SHARP_TOKEN,
JavaDocTokenType.DOC_TAG_VALUE_LT, JavaDocTokenType.DOC_TAG_VALUE_GT);
private static final TokenSet INLINE_TAG_BORDERS_SET = TokenSet.create(
JavaDocTokenType.DOC_INLINE_TAG_START, JavaDocTokenType.DOC_INLINE_TAG_END);
public static final TokenSet SKIP_TOKENS = TokenSet.create(JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS);
@NonNls private static final String SEE_TAG = "@see";
@NonNls private static final String LINK_TAG = "@link";
@NonNls private static final String LINK_PLAIN_TAG = "@linkplain";
@NonNls private static final String THROWS_TAG = "@throws";
@NonNls private static final String EXCEPTION_TAG = "@exception";
@NonNls private static final String PARAM_TAG = "@param";
@NonNls private static final String VALUE_TAG = "@value";
private static final Key<Integer> BRACE_SCOPE_KEY = Key.create("Javadoc.Parser.Brace.Scope");
private JavadocParser() { }
public static void parseJavadocReference(@NotNull final PsiBuilder builder) {
JavaParser.INSTANCE.getReferenceParser().parseJavaCodeReference(builder, true, true, false, false);
swallowTokens(builder);
}
public static void parseJavadocType(@NotNull final PsiBuilder builder) {
JavaParser.INSTANCE.getReferenceParser().parseType(builder, ReferenceParser.EAT_LAST_DOT | ReferenceParser.ELLIPSIS | ReferenceParser.WILDCARD);
swallowTokens(builder);
}
private static void swallowTokens(PsiBuilder builder) {
while (!builder.eof()) builder.advanceLexer();
}
public static void parseDocCommentText(@NotNull final PsiBuilder builder) {
builder.enforceCommentTokens(SKIP_TOKENS);
while (!builder.eof()) {
final IElementType tokenType = getTokenType(builder);
if (tokenType == JavaDocTokenType.DOC_TAG_NAME) {
parseTag(builder);
}
else {
parseDataItem(builder, null, false);
}
}
}
private static void parseTag(@NotNull final PsiBuilder builder) {
final String tagName = builder.getTokenText();
final PsiBuilder.Marker tag = builder.mark();
builder.advanceLexer();
while (true) {
final IElementType tokenType = getTokenType(builder);
if (tokenType == null || tokenType == JavaDocTokenType.DOC_TAG_NAME || tokenType == JavaDocTokenType.DOC_COMMENT_END) break;
parseDataItem(builder, tagName, false);
}
tag.done(JavaDocElementType.DOC_TAG);
}
private static void parseDataItem(@NotNull final PsiBuilder builder, @Nullable final String tagName, final boolean isInline) {
IElementType tokenType = getTokenType(builder);
if (tokenType == JavaDocTokenType.DOC_INLINE_TAG_START) {
int braceScope = getBraceScope(builder);
if (braceScope > 0) {
setBraceScope(builder, braceScope + 1);
builder.remapCurrentToken(JavaDocTokenType.DOC_COMMENT_DATA);
builder.advanceLexer();
return;
}
final PsiBuilder.Marker tag = builder.mark();
builder.advanceLexer();
tokenType = getTokenType(builder);
if (tokenType != JavaDocTokenType.DOC_TAG_NAME && tokenType != JavaDocTokenType.DOC_COMMENT_BAD_CHARACTER) {
tag.rollbackTo();
builder.remapCurrentToken(JavaDocTokenType.DOC_COMMENT_DATA);
builder.advanceLexer();
return;
}
setBraceScope(builder, braceScope + 1);
String inlineTagName = "";
while (true) {
tokenType = getTokenType(builder);
if (tokenType == JavaDocTokenType.DOC_TAG_NAME) {
inlineTagName = builder.getTokenText();
}
else if (tokenType == null || tokenType == JavaDocTokenType.DOC_COMMENT_END) {
break;
}
parseDataItem(builder, inlineTagName, true);
if (tokenType == JavaDocTokenType.DOC_INLINE_TAG_END) {
braceScope = getBraceScope(builder);
if (braceScope > 0) setBraceScope(builder, --braceScope);
if (braceScope == 0) break;
}
}
tag.done(JavaDocElementType.DOC_INLINE_TAG);
}
else if (TAG_VALUES_SET.contains(tokenType)) {
if (SEE_TAG.equals(tagName) && !isInline ||
LINK_TAG.equals(tagName) && isInline) {
parseSeeTagValue(builder);
}
else {
if (JavaParserUtil.getLanguageLevel(builder).isAtLeast(LanguageLevel.JDK_1_4) && LINK_PLAIN_TAG.equals(tagName) && isInline) {
parseSeeTagValue(builder);
}
else if (!isInline && (THROWS_TAG.equals(tagName) || EXCEPTION_TAG.equals(tagName))) {
final PsiBuilder.Marker tagValue = builder.mark();
builder.remapCurrentToken(JavaDocElementType.DOC_REFERENCE_HOLDER);
builder.advanceLexer();
tagValue.done(JavaDocElementType.DOC_TAG_VALUE_ELEMENT);
}
else if (!isInline && tagName != null && tagName.equals(PARAM_TAG)) {
parseSimpleTagValue(builder, true);
}
else {
if (JavaParserUtil.getLanguageLevel(builder).isAtLeast(LanguageLevel.JDK_1_5) && VALUE_TAG.equals(tagName) && isInline) {
parseSeeTagValue(builder);
}
else {
parseSimpleTagValue(builder, false);
}
}
}
}
else {
remapAndAdvance(builder);
}
}
private static void parseSeeTagValue(@NotNull final PsiBuilder builder) {
final IElementType tokenType = getTokenType(builder);
if (tokenType == JavaDocTokenType.DOC_TAG_VALUE_SHARP_TOKEN) {
parseMethodRef(builder, builder.mark());
}
else if (tokenType == JavaDocTokenType.DOC_TAG_VALUE_TOKEN) {
final PsiBuilder.Marker refStart = builder.mark();
builder.remapCurrentToken(JavaDocElementType.DOC_REFERENCE_HOLDER);
builder.advanceLexer();
if (getTokenType(builder) == JavaDocTokenType.DOC_TAG_VALUE_SHARP_TOKEN) {
parseMethodRef(builder, refStart);
}
else {
refStart.drop();
}
}
else {
final PsiBuilder.Marker tagValue = builder.mark();
builder.advanceLexer();
tagValue.done(JavaDocElementType.DOC_TAG_VALUE_ELEMENT);
}
}
private static void parseMethodRef(@NotNull final PsiBuilder builder, @NotNull final PsiBuilder.Marker refStart) {
builder.advanceLexer();
if (getTokenType(builder) != JavaDocTokenType.DOC_TAG_VALUE_TOKEN) {
refStart.done(JavaDocElementType.DOC_METHOD_OR_FIELD_REF);
return;
}
builder.advanceLexer();
if (getTokenType(builder) == JavaDocTokenType.DOC_TAG_VALUE_LPAREN) {
builder.advanceLexer();
final PsiBuilder.Marker subValue = builder.mark();
IElementType tokenType;
while (TAG_VALUES_SET.contains(tokenType = getTokenType(builder))) {
if (tokenType == JavaDocTokenType.DOC_TAG_VALUE_TOKEN) {
builder.remapCurrentToken(JavaDocElementType.DOC_TYPE_HOLDER);
builder.advanceLexer();
while (TAG_VALUES_SET.contains(tokenType = getTokenType(builder)) &&
tokenType != JavaDocTokenType.DOC_TAG_VALUE_COMMA && tokenType != JavaDocTokenType.DOC_TAG_VALUE_RPAREN) {
builder.advanceLexer();
}
}
else if (tokenType == JavaDocTokenType.DOC_TAG_VALUE_RPAREN) {
subValue.done(JavaDocElementType.DOC_TAG_VALUE_ELEMENT);
builder.advanceLexer();
refStart.done(JavaDocElementType.DOC_METHOD_OR_FIELD_REF);
return;
}
else {
builder.advanceLexer();
}
}
subValue.done(JavaDocElementType.DOC_TAG_VALUE_ELEMENT);
}
refStart.done(JavaDocElementType.DOC_METHOD_OR_FIELD_REF);
}
private static void parseSimpleTagValue(@NotNull final PsiBuilder builder, final boolean parameter) {
final PsiBuilder.Marker tagValue = builder.mark();
while (TAG_VALUES_SET.contains(getTokenType(builder))) {
builder.advanceLexer();
}
tagValue.done(parameter ? JavaDocElementType.DOC_PARAMETER_REF : JavaDocElementType.DOC_TAG_VALUE_ELEMENT);
}
@Nullable
private static IElementType getTokenType(@NotNull final PsiBuilder builder) {
IElementType tokenType;
while ((tokenType = builder.getTokenType()) == JavaDocTokenType.DOC_SPACE) {
builder.remapCurrentToken(TokenType.WHITE_SPACE);
builder.advanceLexer();
}
return tokenType;
}
private static int getBraceScope(@NotNull final PsiBuilder builder) {
final Integer braceScope = builder.getUserDataUnprotected(BRACE_SCOPE_KEY);
return braceScope != null ? braceScope : 0;
}
private static void setBraceScope(@NotNull final PsiBuilder builder, final int braceScope) {
builder.putUserDataUnprotected(BRACE_SCOPE_KEY, braceScope);
}
private static void remapAndAdvance(@NotNull final PsiBuilder builder) {
if (INLINE_TAG_BORDERS_SET.contains(builder.getTokenType()) && getBraceScope(builder) != 1) {
builder.remapCurrentToken(JavaDocTokenType.DOC_COMMENT_DATA);
}
builder.advanceLexer();
}
}