/* * Copyright 2000-2013 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.codeInsight.daemon.JavaErrorMessages; import com.intellij.lang.PsiBuilder; import com.intellij.openapi.util.Pair; import com.intellij.psi.JavaTokenType; import com.intellij.psi.impl.source.tree.ElementType; import com.intellij.psi.impl.source.tree.JavaElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.ILazyParseableElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; import static com.intellij.lang.PsiBuilderUtil.expect; import static com.intellij.lang.java.parser.JavaParserUtil.*; public class DeclarationParser { public enum Context { FILE, CLASS, CODE_BLOCK, ANNOTATION_INTERFACE } private static final TokenSet AFTER_END_DECLARATION_SET = TokenSet.create( JavaElementType.FIELD, JavaElementType.METHOD); private static final TokenSet BEFORE_LBRACE_ELEMENTS_SET = TokenSet.create( JavaTokenType.IDENTIFIER, JavaTokenType.COMMA, JavaTokenType.EXTENDS_KEYWORD, JavaTokenType.IMPLEMENTS_KEYWORD); private static final TokenSet APPEND_TO_METHOD_SET = TokenSet.create( JavaTokenType.IDENTIFIER, JavaTokenType.COMMA, JavaTokenType.THROWS_KEYWORD); private static final TokenSet PARAM_LIST_STOPPERS = TokenSet.create( JavaTokenType.RPARENTH, JavaTokenType.LBRACE, JavaTokenType.ARROW); private static final String WHITESPACES = "\n\r \t"; private static final String LINE_ENDS = "\n\r"; private final JavaParser myParser; public DeclarationParser(@NotNull final JavaParser javaParser) { myParser = javaParser; } public void parseClassBodyWithBraces(final PsiBuilder builder, final boolean isAnnotation, final boolean isEnum) { assert builder.getTokenType() == JavaTokenType.LBRACE : builder.getTokenType(); builder.advanceLexer(); final PsiBuilder builderWrapper = braceMatchingBuilder(builder); if (isEnum) { parseEnumConstants(builderWrapper); } parseClassBodyDeclarations(builderWrapper, isAnnotation); expectOrError(builder, JavaTokenType.RBRACE, "expected.rbrace"); } @Nullable public PsiBuilder.Marker parseClassFromKeyword(final PsiBuilder builder, final PsiBuilder.Marker declaration, final boolean isAnnotation, final Context context) { final IElementType keywordTokenType = builder.getTokenType(); assert ElementType.CLASS_KEYWORD_BIT_SET.contains(keywordTokenType) : keywordTokenType; builder.advanceLexer(); final boolean isEnum = (keywordTokenType == JavaTokenType.ENUM_KEYWORD); if (!expect(builder, JavaTokenType.IDENTIFIER)) { error(builder, JavaErrorMessages.message("expected.identifier")); declaration.drop(); return null; } final ReferenceParser refParser = myParser.getReferenceParser(); refParser.parseTypeParameters(builder); refParser.parseReferenceList(builder, JavaTokenType.EXTENDS_KEYWORD, JavaElementType.EXTENDS_LIST, JavaTokenType.COMMA); refParser.parseReferenceList(builder, JavaTokenType.IMPLEMENTS_KEYWORD, JavaElementType.IMPLEMENTS_LIST, JavaTokenType.COMMA); if (builder.getTokenType() != JavaTokenType.LBRACE) { final PsiBuilder.Marker error = builder.mark(); while (BEFORE_LBRACE_ELEMENTS_SET.contains(builder.getTokenType())) { builder.advanceLexer(); } error.error(JavaErrorMessages.message("expected.lbrace")); } if (builder.getTokenType() == JavaTokenType.LBRACE) { parseClassBodyWithBraces(builder, isAnnotation, isEnum); } if (context == Context.FILE) { boolean declarationsAfterEnd = false; while (builder.getTokenType() != null && builder.getTokenType() != JavaTokenType.RBRACE) { final PsiBuilder.Marker position = builder.mark(); final PsiBuilder.Marker extra = parse(builder, Context.CLASS); if (extra != null && AFTER_END_DECLARATION_SET.contains(exprType(extra))) { if (!declarationsAfterEnd) { error(builder, JavaErrorMessages.message("expected.class.or.interface"), extra); } declarationsAfterEnd = true; position.drop(); } else { position.rollbackTo(); break; } } if (declarationsAfterEnd) { expectOrError(builder, JavaTokenType.RBRACE, "expected.rbrace"); } } done(declaration, JavaElementType.CLASS); return declaration; } private void parseEnumConstants(final PsiBuilder builder) { while (builder.getTokenType() != null) { if (expect(builder, JavaTokenType.SEMICOLON)) { return; } if (builder.getTokenType() == JavaTokenType.PRIVATE_KEYWORD || builder.getTokenType() == JavaTokenType.PROTECTED_KEYWORD) { error(builder, JavaErrorMessages.message("expected.semicolon")); return; } final PsiBuilder.Marker enumConstant = parseEnumConstant(builder); if (enumConstant == null) { error(builder, JavaErrorMessages.message("expected.identifier")); } if (!expect(builder, JavaTokenType.COMMA)) { if (builder.getTokenType() != null && builder.getTokenType() != JavaTokenType.SEMICOLON) { error(builder, JavaErrorMessages.message("expected.comma.or.semicolon")); return; } } } } @Nullable public PsiBuilder.Marker parseEnumConstant(final PsiBuilder builder) { final PsiBuilder.Marker constant = builder.mark(); parseModifierList(builder); if (expect(builder, JavaTokenType.IDENTIFIER)) { if (builder.getTokenType() == JavaTokenType.LPARENTH) { myParser.getExpressionParser().parseArgumentList(builder); } else { emptyElement(builder, JavaElementType.EXPRESSION_LIST); } if (builder.getTokenType() == JavaTokenType.LBRACE) { final PsiBuilder.Marker constantInit = builder.mark(); parseClassBodyWithBraces(builder, false, false); done(constantInit, JavaElementType.ENUM_CONSTANT_INITIALIZER); } done(constant, JavaElementType.ENUM_CONSTANT); return constant; } else { constant.rollbackTo(); return null; } } public void parseClassBodyDeclarations(final PsiBuilder builder, final boolean isAnnotation) { final Context context = isAnnotation ? Context.ANNOTATION_INTERFACE : Context.CLASS; PsiBuilder.Marker invalidElements = null; while (true) { final IElementType tokenType = builder.getTokenType(); if (tokenType == null || tokenType == JavaTokenType.RBRACE) break; if (tokenType == JavaTokenType.SEMICOLON) { if (invalidElements != null) { invalidElements.error(JavaErrorMessages.message("unexpected.token")); invalidElements = null; } builder.advanceLexer(); continue; } final PsiBuilder.Marker declaration = parse(builder, context); if (declaration != null) { if (invalidElements != null) { invalidElements.errorBefore(JavaErrorMessages.message("unexpected.token"), declaration); invalidElements = null; } continue; } if (invalidElements == null) { invalidElements = builder.mark(); } // adding a reference, not simple tokens allows "Browse ..." to work well final PsiBuilder.Marker ref = myParser.getReferenceParser().parseJavaCodeReference(builder, true, true, false, false); if (ref == null) { builder.advanceLexer(); } } if (invalidElements != null) { invalidElements.error(JavaErrorMessages.message("unexpected.token")); } } @Nullable public PsiBuilder.Marker parse(final PsiBuilder builder, final Context context) { final IElementType tokenType = builder.getTokenType(); if (tokenType == null) return null; if (tokenType == JavaTokenType.LBRACE) { if (context == Context.FILE || context == Context.CODE_BLOCK) return null; } else if (tokenType == JavaTokenType.IDENTIFIER || ElementType.PRIMITIVE_TYPE_BIT_SET.contains(tokenType)) { if (context == Context.FILE) return null; } else if (tokenType instanceof ILazyParseableElementType) { builder.advanceLexer(); return null; } else if (!ElementType.MODIFIER_BIT_SET.contains(tokenType) && !ElementType.CLASS_KEYWORD_BIT_SET.contains(tokenType) && tokenType != JavaTokenType.AT && (context == Context.CODE_BLOCK || tokenType != JavaTokenType.LT)) { return null; } final PsiBuilder.Marker declaration = builder.mark(); final int declarationStart = builder.getCurrentOffset(); final Pair<PsiBuilder.Marker, Boolean> modListInfo = parseModifierList(builder); final PsiBuilder.Marker modList = modListInfo.first; if (expect(builder, JavaTokenType.AT)) { if (builder.getTokenType() == JavaTokenType.INTERFACE_KEYWORD) { final PsiBuilder.Marker result = parseClassFromKeyword(builder, declaration, true, context); return result != null ? result : modList; } else { declaration.rollbackTo(); return null; } } else if (ElementType.CLASS_KEYWORD_BIT_SET.contains(builder.getTokenType())) { final PsiBuilder.Marker result = parseClassFromKeyword(builder, declaration, false, context); return result != null ? result : modList; } PsiBuilder.Marker typeParams = null; if (builder.getTokenType() == JavaTokenType.LT) { typeParams = myParser.getReferenceParser().parseTypeParameters(builder); } if (context == Context.FILE) { error(builder, JavaErrorMessages.message("expected.class.or.interface"), typeParams); declaration.drop(); return modList; } PsiBuilder.Marker type; if (ElementType.PRIMITIVE_TYPE_BIT_SET.contains(builder.getTokenType())) { type = parseTypeNotNull(builder); } else if (builder.getTokenType() == JavaTokenType.IDENTIFIER) { final PsiBuilder.Marker idPos = builder.mark(); type = parseTypeNotNull(builder); if (builder.getTokenType() == JavaTokenType.LPARENTH) { // constructor if (context == Context.CODE_BLOCK) { declaration.rollbackTo(); return null; } idPos.rollbackTo(); if (typeParams == null) { emptyElement(builder, JavaElementType.TYPE_PARAMETER_LIST); } builder.advanceLexer(); if (builder.getTokenType() != JavaTokenType.LPARENTH) { declaration.rollbackTo(); return null; } return parseMethodFromLeftParenth(builder, declaration, false, true); } idPos.drop(); } else if (builder.getTokenType() == JavaTokenType.LBRACE) { if (context == Context.CODE_BLOCK) { error(builder, JavaErrorMessages.message("expected.identifier.or.type"), typeParams); declaration.drop(); return modList; } final PsiBuilder.Marker codeBlock = myParser.getStatementParser().parseCodeBlock(builder); assert codeBlock != null : builder.getOriginalText(); if (typeParams != null) { final PsiBuilder.Marker error = typeParams.precede(); error.errorBefore(JavaErrorMessages.message("unexpected.token"), codeBlock); } done(declaration, JavaElementType.CLASS_INITIALIZER); return declaration; } else { final PsiBuilder.Marker error; if (typeParams != null) { error = typeParams.precede(); } else { error = builder.mark(); } error.error(JavaErrorMessages.message("expected.identifier.or.type")); declaration.drop(); return modList; } if (!expect(builder, JavaTokenType.IDENTIFIER)) { if ((context == Context.CODE_BLOCK) && Boolean.TRUE.equals(modListInfo.second)) { declaration.rollbackTo(); return null; } else { if (typeParams != null) { typeParams.precede().errorBefore(JavaErrorMessages.message("unexpected.token"), type); } builder.error(JavaErrorMessages.message("expected.identifier")); declaration.drop(); return modList; } } if (builder.getTokenType() == JavaTokenType.LPARENTH) { if (context == Context.CLASS || context == Context.ANNOTATION_INTERFACE) { // method if (typeParams == null) { emptyElement(type, JavaElementType.TYPE_PARAMETER_LIST); } return parseMethodFromLeftParenth(builder, declaration, (context == Context.ANNOTATION_INTERFACE), false); } } if (typeParams != null) { typeParams.precede().errorBefore(JavaErrorMessages.message("unexpected.token"), type); } return parseFieldOrLocalVariable(builder, declaration, declarationStart, context); } @NotNull private PsiBuilder.Marker parseTypeNotNull(final PsiBuilder builder) { final PsiBuilder.Marker type = myParser.getReferenceParser().parseType(builder, ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD); assert type != null : builder.getOriginalText(); return type; } @NotNull public Pair<PsiBuilder.Marker, Boolean> parseModifierList(final PsiBuilder builder) { return parseModifierList(builder, ElementType.MODIFIER_BIT_SET); } @NotNull public Pair<PsiBuilder.Marker, Boolean> parseModifierList(final PsiBuilder builder, final TokenSet modifiers) { final PsiBuilder.Marker modList = builder.mark(); boolean isEmpty = true; while (true) { final IElementType tokenType = builder.getTokenType(); if (tokenType == null) break; if (modifiers.contains(tokenType)) { builder.advanceLexer(); isEmpty = false; } else if (tokenType == JavaTokenType.AT) { if (ElementType.KEYWORD_BIT_SET.contains(builder.lookAhead(1))) { break; } parseAnnotation(builder); isEmpty = false; } else { break; } } done(modList, JavaElementType.MODIFIER_LIST); return Pair.create(modList, isEmpty); } private PsiBuilder.Marker parseMethodFromLeftParenth(PsiBuilder builder, PsiBuilder.Marker declaration, boolean anno, boolean constructor) { parseParameterList(builder); eatBrackets(builder, constructor ? "expected.semicolon" : null); myParser.getReferenceParser().parseReferenceList(builder, JavaTokenType.THROWS_KEYWORD, JavaElementType.THROWS_LIST, JavaTokenType.COMMA); if (anno && expect(builder, JavaTokenType.DEFAULT_KEYWORD)) { parseAnnotationValue(builder); } IElementType tokenType = builder.getTokenType(); if (tokenType != JavaTokenType.SEMICOLON && tokenType != JavaTokenType.LBRACE) { PsiBuilder.Marker error = builder.mark(); // heuristic: going to next line obviously means method signature is over, starting new method (actually, another one completion hack) CharSequence text = builder.getOriginalText(); Loop: while (true) { for (int i = builder.getCurrentOffset() - 1; i >= 0; i--) { char ch = text.charAt(i); if (ch == '\n') break Loop; else if (ch != ' ' && ch != '\t') break; } if (!expect(builder, APPEND_TO_METHOD_SET)) break; } error.error(JavaErrorMessages.message("expected.lbrace.or.semicolon")); } if (!expect(builder, JavaTokenType.SEMICOLON)) { if (builder.getTokenType() == JavaTokenType.LBRACE) { myParser.getStatementParser().parseCodeBlock(builder); } } done(declaration, anno ? JavaElementType.ANNOTATION_METHOD : JavaElementType.METHOD); return declaration; } @NotNull public PsiBuilder.Marker parseParameterList(PsiBuilder builder) { return parseElementList(builder, ListType.METHOD); } @NotNull public PsiBuilder.Marker parseResourceList(PsiBuilder builder) { return parseElementList(builder, ListType.RESOURCE); } @NotNull public PsiBuilder.Marker parseLambdaParameterList(PsiBuilder builder, boolean typed) { return parseElementList(builder, typed ? ListType.LAMBDA_TYPED : ListType.LAMBDA_UNTYPED); } private enum ListType {METHOD, RESOURCE, LAMBDA_TYPED, LAMBDA_UNTYPED} @NotNull private PsiBuilder.Marker parseElementList(PsiBuilder builder, ListType type) { final boolean lambda = (type == ListType.LAMBDA_TYPED || type == ListType.LAMBDA_UNTYPED); final boolean resources = (type == ListType.RESOURCE); final PsiBuilder.Marker elementList = builder.mark(); final boolean leftParenth = expect(builder, JavaTokenType.LPARENTH); assert lambda || leftParenth : builder.getTokenType(); final IElementType delimiter = resources ? JavaTokenType.SEMICOLON : JavaTokenType.COMMA; final String noDelimiterMsg = resources ? "expected.semicolon" : "expected.comma"; final String noElementMsg = resources ? "expected.resource" : "expected.parameter"; PsiBuilder.Marker invalidElements = null; String errorMessage = null; boolean delimiterExpected = false; boolean noElements = true; while (true) { final IElementType tokenType = builder.getTokenType(); if (tokenType == null || PARAM_LIST_STOPPERS.contains(tokenType)) { final boolean noLastElement = !delimiterExpected && (!noElements && !resources || noElements && resources); if (noLastElement) { final String key = lambda ? "expected.parameter" : "expected.identifier.or.type"; error(builder, JavaErrorMessages.message(key)); } if (tokenType == JavaTokenType.RPARENTH) { if (invalidElements != null) { invalidElements.error(errorMessage); invalidElements = null; } builder.advanceLexer(); } else { if (!noLastElement || resources) { if (invalidElements != null) { invalidElements.error(errorMessage); } invalidElements = null; if (leftParenth) { error(builder, JavaErrorMessages.message("expected.rparen")); } } } break; } if (delimiterExpected) { if (builder.getTokenType() == delimiter) { delimiterExpected = false; if (invalidElements != null) { invalidElements.error(errorMessage); invalidElements = null; } builder.advanceLexer(); continue; } } else { final PsiBuilder.Marker listElement = resources ? parseResource(builder) : lambda ? parseLambdaParameter(builder, type == ListType.LAMBDA_TYPED) : parseParameter(builder, true, false); if (listElement != null) { delimiterExpected = true; if (invalidElements != null) { invalidElements.errorBefore(errorMessage, listElement); invalidElements = null; } noElements= false; continue; } } if (invalidElements == null) { if (builder.getTokenType() == delimiter) { error(builder, JavaErrorMessages.message(noElementMsg)); builder.advanceLexer(); if (noElements && resources) { noElements = false; } continue; } else { invalidElements = builder.mark(); errorMessage = JavaErrorMessages.message(delimiterExpected ? noDelimiterMsg : noElementMsg); } } // adding a reference, not simple tokens allows "Browse .." to work well final PsiBuilder.Marker ref = myParser.getReferenceParser().parseJavaCodeReference(builder, true, true, false, false); if (ref == null && builder.getTokenType() != null) { builder.advanceLexer(); } } if (invalidElements != null) { invalidElements.error(errorMessage); } done(elementList, resources ? JavaElementType.RESOURCE_LIST : JavaElementType.PARAMETER_LIST); return elementList; } @Nullable public PsiBuilder.Marker parseParameter(PsiBuilder builder, boolean ellipsis, boolean disjunctiveType) { return parseListElement(builder, true, ellipsis, disjunctiveType, false); } @Nullable public PsiBuilder.Marker parseResource(PsiBuilder builder) { return parseListElement(builder, true, false, false, true); } @Nullable public PsiBuilder.Marker parseLambdaParameter(PsiBuilder builder, boolean typed) { return parseListElement(builder, typed, true, false, false); } @Nullable private PsiBuilder.Marker parseListElement(PsiBuilder builder, boolean typed, boolean ellipsis, boolean disjunctiveType, boolean resource) { PsiBuilder.Marker param = builder.mark(); Pair<PsiBuilder.Marker, Boolean> modListInfo = parseModifierList(builder); ReferenceParser.TypeInfo typeInfo = null; if (typed) { int flags = ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD; if (ellipsis) flags |= ReferenceParser.ELLIPSIS; if (disjunctiveType) flags |= ReferenceParser.DISJUNCTIONS; typeInfo = myParser.getReferenceParser().parseTypeInfo(builder, flags); if (typeInfo == null) { if (Boolean.TRUE.equals(modListInfo.second)) { param.rollbackTo(); return null; } else { error(builder, JavaErrorMessages.message("expected.type")); emptyElement(builder, JavaElementType.TYPE); } } } if (expect(builder, JavaTokenType.IDENTIFIER)) { if (!resource) { eatBrackets(builder, typeInfo != null && typeInfo.isVarArg ? "expected.rparen" : null); done(param, JavaElementType.PARAMETER); return param; } } else { error(builder, JavaErrorMessages.message("expected.identifier")); param.drop(); return modListInfo.first; } if (expectOrError(builder, JavaTokenType.EQ, "expected.eq")) { if (myParser.getExpressionParser().parse(builder) == null) { error(builder, JavaErrorMessages.message("expected.expression")); } } done(param, JavaElementType.RESOURCE_VARIABLE); return param; } @Nullable private PsiBuilder.Marker parseFieldOrLocalVariable(PsiBuilder builder, PsiBuilder.Marker declaration, int declarationStart, Context context) { final IElementType varType; if (context == Context.CLASS || context == Context.ANNOTATION_INTERFACE) { varType = JavaElementType.FIELD; } else if (context == Context.CODE_BLOCK) { varType = JavaElementType.LOCAL_VARIABLE; } else { declaration.drop(); assert false : "Unexpected context: " + context; return null; } PsiBuilder.Marker variable = declaration; boolean unclosed = false; boolean eatSemicolon = true; boolean shouldRollback; boolean openMarker = true; while (true) { shouldRollback = true; if (!eatBrackets(builder, null)) { unclosed = true; } if (expect(builder, JavaTokenType.EQ)) { final PsiBuilder.Marker expr = myParser.getExpressionParser().parse(builder); if (expr != null) { shouldRollback = false; } else { error(builder, JavaErrorMessages.message("expected.expression")); unclosed = true; break; } } if (builder.getTokenType() != JavaTokenType.COMMA) break; done(variable, varType); builder.advanceLexer(); if (builder.getTokenType() != JavaTokenType.IDENTIFIER) { error(builder, JavaErrorMessages.message("expected.identifier")); unclosed = true; eatSemicolon = false; openMarker = false; break; } variable = builder.mark(); builder.advanceLexer(); } if (builder.getTokenType() == JavaTokenType.SEMICOLON && eatSemicolon) { builder.advanceLexer(); } else { // special treatment (see DeclarationParserTest.testMultiLineUnclosed()) if (!builder.eof() && shouldRollback) { final CharSequence text = builder.getOriginalText(); final int spaceEnd = builder.getCurrentOffset(); final int spaceStart = CharArrayUtil.shiftBackward(text, spaceEnd-1, WHITESPACES); final int lineStart = CharArrayUtil.shiftBackwardUntil(text, spaceEnd, LINE_ENDS); if (declarationStart < lineStart && lineStart < spaceStart) { final int newBufferEnd = CharArrayUtil.shiftForward(text, lineStart, WHITESPACES); declaration.rollbackTo(); return parse(stoppingBuilder(builder, newBufferEnd), context); } } if (!unclosed) { error(builder, JavaErrorMessages.message("expected.semicolon")); } } if (openMarker) { done(variable, varType); } return declaration; } private boolean eatBrackets(PsiBuilder builder, @Nullable @PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String errorKey) { IElementType tokenType = builder.getTokenType(); if (tokenType != JavaTokenType.LBRACKET && tokenType != JavaTokenType.AT) return true; PsiBuilder.Marker marker = builder.mark(); int count = 0; while (true) { parseAnnotations(builder); if (!expect(builder, JavaTokenType.LBRACKET)) { break; } ++count; if (!expect(builder, JavaTokenType.RBRACKET)) { break; } ++count; } if (count == 0) { // just annotation, most probably belongs to a next declaration marker.rollbackTo(); return true; } if (errorKey != null) { marker.error(JavaErrorMessages.message(errorKey)); } else { marker.drop(); } boolean paired = count % 2 == 0; if (!paired) { error(builder, JavaErrorMessages.message("expected.rbracket")); } return paired; } @Nullable public PsiBuilder.Marker parseAnnotations(final PsiBuilder builder) { PsiBuilder.Marker firstAnno = null; while (builder.getTokenType() == JavaTokenType.AT) { final PsiBuilder.Marker anno = parseAnnotation(builder); if (firstAnno == null) firstAnno = anno; } return firstAnno; } @NotNull public PsiBuilder.Marker parseAnnotation(final PsiBuilder builder) { assert builder.getTokenType() == JavaTokenType.AT : builder.getTokenType(); final PsiBuilder.Marker anno = builder.mark(); builder.advanceLexer(); final PsiBuilder.Marker classRef = myParser.getReferenceParser().parseJavaCodeReference(builder, true, false, false, false); if (classRef == null) { error(builder, JavaErrorMessages.message("expected.class.reference")); } parseAnnotationParameterList(builder); done(anno, JavaElementType.ANNOTATION); return anno; } @NotNull private PsiBuilder.Marker parseAnnotationParameterList(final PsiBuilder builder) { PsiBuilder.Marker list = builder.mark(); if (!expect(builder, JavaTokenType.LPARENTH)) { done(list, JavaElementType.ANNOTATION_PARAMETER_LIST); return list; } if (expect(builder, JavaTokenType.RPARENTH)) { done(list, JavaElementType.ANNOTATION_PARAMETER_LIST); return list; } final boolean isFirstParamNamed = parseAnnotationParameter(builder, true); boolean isFirstParamWarned = false; boolean afterBad = false; while (true) { final IElementType tokenType = builder.getTokenType(); if (tokenType == null) { error(builder, JavaErrorMessages.message("expected.parameter")); break; } else if (expect(builder, JavaTokenType.RPARENTH)) { break; } else if (tokenType == JavaTokenType.COMMA) { final PsiBuilder.Marker errorStart = builder.mark(); final PsiBuilder.Marker errorEnd = builder.mark(); builder.advanceLexer(); final boolean hasParamName = parseAnnotationParameter(builder, false); if (!isFirstParamNamed && hasParamName && !isFirstParamWarned) { errorStart.errorBefore(JavaErrorMessages.message("annotation.name.is.missing"), errorEnd); isFirstParamWarned = true; } else { errorStart.drop(); } errorEnd.drop(); } else if (!afterBad) { error(builder, JavaErrorMessages.message("expected.comma.or.rparen")); builder.advanceLexer(); afterBad = true; } else { afterBad = false; parseAnnotationParameter(builder, false); } } done(list, JavaElementType.ANNOTATION_PARAMETER_LIST); return list; } private boolean parseAnnotationParameter(final PsiBuilder builder, final boolean mayBeSimple) { PsiBuilder.Marker pair = builder.mark(); if (mayBeSimple) { parseAnnotationValue(builder); if (builder.getTokenType() != JavaTokenType.EQ) { done(pair, JavaElementType.NAME_VALUE_PAIR); return false; } pair.rollbackTo(); pair = builder.mark(); } final boolean hasName = expectOrError(builder, JavaTokenType.IDENTIFIER, "expected.identifier"); expectOrError(builder, JavaTokenType.EQ, "expected.eq"); parseAnnotationValue(builder); done(pair, JavaElementType.NAME_VALUE_PAIR); return hasName; } @NotNull public PsiBuilder.Marker parseAnnotationValue(final PsiBuilder builder) { PsiBuilder.Marker result; final IElementType tokenType = builder.getTokenType(); if (tokenType == JavaTokenType.AT) { result = parseAnnotation(builder); } else if (tokenType == JavaTokenType.LBRACE) { result = parseAnnotationArrayInitializer(builder); } else { result = myParser.getExpressionParser().parseConditional(builder); } if (result == null) { result = builder.mark(); result.error(JavaErrorMessages.message("expected.value")); } return result; } @NotNull private PsiBuilder.Marker parseAnnotationArrayInitializer(final PsiBuilder builder) { assert builder.getTokenType() == JavaTokenType.LBRACE : builder.getTokenType(); final PsiBuilder.Marker annoArray = builder.mark(); builder.advanceLexer(); if (expect(builder, JavaTokenType.RBRACE)) { done(annoArray, JavaElementType.ANNOTATION_ARRAY_INITIALIZER); return annoArray; } parseAnnotationValue(builder); boolean unclosed = false; while (true) { if (expect(builder, JavaTokenType.RBRACE)) { break; } else if (expect(builder, JavaTokenType.COMMA)) { if (builder.getTokenType() != JavaTokenType.RBRACE) { parseAnnotationValue(builder); } } else { error(builder, JavaErrorMessages.message("expected.rbrace")); unclosed = true; break; } } done(annoArray, JavaElementType.ANNOTATION_ARRAY_INITIALIZER); if (unclosed) { annoArray.setCustomEdgeTokenBinders(null, GREEDY_RIGHT_EDGE_PROCESSOR); } return annoArray; } }