/* * 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(); } }