blob: 36d024726bbfc888c08044ff16dea2e86ab04519 [file] [log] [blame]
/*
* 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.lang.WhitespacesBinders;
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 TokenSet TYPE_START = TokenSet.orSet(
ElementType.PRIMITIVE_TYPE_BIT_SET, TokenSet.create(JavaTokenType.IDENTIFIER, JavaTokenType.AT));
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;
}
if (builder.getTokenType() == JavaTokenType.LBRACE) {
if (context == Context.CODE_BLOCK) {
error(builder, JavaErrorMessages.message("expected.identifier.or.type"), typeParams);
declaration.drop();
return modList;
}
PsiBuilder.Marker codeBlock = myParser.getStatementParser().parseCodeBlock(builder);
assert codeBlock != null : builder.getOriginalText();
if (typeParams != null) {
PsiBuilder.Marker error = typeParams.precede();
error.errorBefore(JavaErrorMessages.message("unexpected.token"), codeBlock);
}
done(declaration, JavaElementType.CLASS_INITIALIZER);
return declaration;
}
PsiBuilder.Marker type = null;
if (TYPE_START.contains(builder.getTokenType())) {
PsiBuilder.Marker pos = builder.mark();
type = myParser.getReferenceParser().parseType(builder, ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD);
if (type == null) {
pos.rollbackTo();
}
else if (builder.getTokenType() == JavaTokenType.LPARENTH) { // constructor
if (context == Context.CODE_BLOCK) {
declaration.rollbackTo();
return null;
}
pos.rollbackTo();
if (typeParams == null) {
emptyElement(builder, JavaElementType.TYPE_PARAMETER_LIST);
}
parseAnnotations(builder);
builder.advanceLexer();
if (builder.getTokenType() == JavaTokenType.LPARENTH) {
return parseMethodFromLeftParenth(builder, declaration, false, true);
}
else {
declaration.rollbackTo();
return null;
}
}
else {
pos.drop();
}
}
if (type == null) {
PsiBuilder.Marker error = typeParams != null ? typeParams.precede() : 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
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, WhitespacesBinders.GREEDY_RIGHT_BINDER);
}
return annoArray;
}
}