blob: c6c8e71fbca1c6a907baa1e44fb46c7a268b7217 [file] [log] [blame]
/*
* Copyright 2000-2014 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.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.TokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.lang.PsiBuilderUtil.expect;
import static com.intellij.lang.java.parser.JavaParserUtil.*;
import static com.intellij.util.BitUtil.*;
public class ReferenceParser {
public static final int EAT_LAST_DOT = 0x01;
public static final int ELLIPSIS = 0x02;
public static final int WILDCARD = 0x04;
public static final int DIAMONDS = 0x08;
public static final int DISJUNCTIONS = 0x10;
public static final int CONJUNCTIONS = 0x20;
public static final int INCOMPLETE_ANNO = 0x40;
public static class TypeInfo {
public boolean isPrimitive = false;
public boolean isParameterized = false;
public boolean isArray = false;
public boolean isVarArg = false;
public boolean hasErrors = false;
public PsiBuilder.Marker marker = null;
}
private static final TokenSet WILDCARD_KEYWORD_SET = TokenSet.create(JavaTokenType.EXTENDS_KEYWORD, JavaTokenType.SUPER_KEYWORD);
private final JavaParser myParser;
public ReferenceParser(@NotNull final JavaParser javaParser) {
myParser = javaParser;
}
@Nullable
public PsiBuilder.Marker parseType(final PsiBuilder builder, final int flags) {
final TypeInfo typeInfo = parseTypeInfo(builder, flags);
return typeInfo != null ? typeInfo.marker : null;
}
@Nullable
public TypeInfo parseTypeInfo(final PsiBuilder builder, final int flags) {
final TypeInfo typeInfo = parseTypeInfo(builder, flags, false);
if (typeInfo != null) {
assert notSet(flags, DISJUNCTIONS|CONJUNCTIONS) : "don't set both flags simultaneously";
final IElementType operator = isSet(flags, DISJUNCTIONS) ? JavaTokenType.OR : isSet(flags, CONJUNCTIONS) ? JavaTokenType.AND : null;
if (operator != null && builder.getTokenType() == operator) {
typeInfo.marker = typeInfo.marker.precede();
while (builder.getTokenType() == operator) {
builder.advanceLexer();
IElementType tokenType = builder.getTokenType();
if (tokenType != JavaTokenType.IDENTIFIER && tokenType != JavaTokenType.AT) {
error(builder, JavaErrorMessages.message("expected.identifier"));
}
parseTypeInfo(builder, flags, false);
}
typeInfo.marker.done(JavaElementType.TYPE);
}
}
return typeInfo;
}
@Nullable
private TypeInfo parseTypeInfo(PsiBuilder builder, int flags, boolean badWildcard) {
if (builder.getTokenType() == null) return null;
final TypeInfo typeInfo = new TypeInfo();
PsiBuilder.Marker type = builder.mark();
PsiBuilder.Marker anno = myParser.getDeclarationParser().parseAnnotations(builder);
final IElementType tokenType = builder.getTokenType();
if (expect(builder, ElementType.PRIMITIVE_TYPE_BIT_SET)) {
typeInfo.isPrimitive = true;
}
else if (tokenType == JavaTokenType.IDENTIFIER) {
parseJavaCodeReference(builder, isSet(flags, EAT_LAST_DOT), true, false, false, false, isSet(flags, DIAMONDS), typeInfo);
}
else if ((isSet(flags, WILDCARD) || badWildcard) && tokenType == JavaTokenType.QUEST) {
builder.advanceLexer();
completeWildcardType(builder, isSet(flags, WILDCARD), type);
typeInfo.marker = type;
return typeInfo;
}
else if (isSet(flags, DIAMONDS) && tokenType == JavaTokenType.GT) {
emptyElement(builder, JavaElementType.DIAMOND_TYPE);
type.done(JavaElementType.TYPE);
typeInfo.marker = type;
return typeInfo;
}
else {
type.drop();
if (anno != null && isSet(flags, INCOMPLETE_ANNO)) {
error(builder, JavaErrorMessages.message("expected.type"));
typeInfo.hasErrors = true;
return typeInfo;
}
return null;
}
while (true) {
type.done(JavaElementType.TYPE);
myParser.getDeclarationParser().parseAnnotations(builder);
final PsiBuilder.Marker bracket = builder.mark();
if (!expect(builder, JavaTokenType.LBRACKET)) {
bracket.drop();
break;
}
if (!expect(builder, JavaTokenType.RBRACKET)) {
bracket.rollbackTo();
break;
}
bracket.drop();
typeInfo.isArray = true;
type = type.precede();
}
if (isSet(flags, ELLIPSIS) && builder.getTokenType() == JavaTokenType.ELLIPSIS) {
type = type.precede();
builder.advanceLexer();
type.done(JavaElementType.TYPE);
typeInfo.isVarArg = true;
}
typeInfo.marker = type;
return typeInfo;
}
private void completeWildcardType(PsiBuilder builder, boolean wildcard, PsiBuilder.Marker type) {
if (expect(builder, WILDCARD_KEYWORD_SET)) {
if (parseTypeInfo(builder, EAT_LAST_DOT) == null) {
error(builder, JavaErrorMessages.message("expected.type"));
}
}
if (wildcard) {
type.done(JavaElementType.TYPE);
}
else {
type.error(JavaErrorMessages.message("wildcard.not.expected"));
}
}
@Nullable
public PsiBuilder.Marker parseJavaCodeReference(PsiBuilder builder, boolean eatLastDot, boolean parameterList, boolean isNew, boolean diamonds) {
return parseJavaCodeReference(builder, eatLastDot, parameterList, false, false, isNew, diamonds, new TypeInfo());
}
public boolean parseImportCodeReference(final PsiBuilder builder, final boolean isStatic) {
final TypeInfo typeInfo = new TypeInfo();
parseJavaCodeReference(builder, true, false, true, isStatic, false, false, typeInfo);
return !typeInfo.hasErrors;
}
@Nullable
private PsiBuilder.Marker parseJavaCodeReference(PsiBuilder builder, boolean eatLastDot, boolean parameterList, boolean isImport,
boolean isStaticImport, boolean isNew, boolean diamonds, TypeInfo typeInfo) {
PsiBuilder.Marker refElement = builder.mark();
myParser.getDeclarationParser().parseAnnotations(builder);
if (!expect(builder, JavaTokenType.IDENTIFIER)) {
refElement.rollbackTo();
if (isImport) {
error(builder, JavaErrorMessages.message("expected.identifier"));
}
typeInfo.hasErrors = true;
return null;
}
if (parameterList) {
typeInfo.isParameterized = parseReferenceParameterList(builder, true, diamonds);
}
else {
emptyElement(builder, JavaElementType.REFERENCE_PARAMETER_LIST);
}
boolean hasIdentifier;
while (builder.getTokenType() == JavaTokenType.DOT) {
refElement.done(JavaElementType.JAVA_CODE_REFERENCE);
if (isNew && !diamonds && typeInfo.isParameterized) {
return refElement;
}
final PsiBuilder.Marker dotPos = builder.mark();
builder.advanceLexer();
myParser.getDeclarationParser().parseAnnotations(builder);
if (expect(builder, JavaTokenType.IDENTIFIER)) {
hasIdentifier = true;
}
else if (isImport && expect(builder, JavaTokenType.ASTERISK)) {
dotPos.drop();
return refElement;
}
else {
if (!eatLastDot) {
dotPos.rollbackTo();
return refElement;
}
hasIdentifier = false;
}
dotPos.drop();
final PsiBuilder.Marker prevElement = refElement;
refElement = refElement.precede();
if (!hasIdentifier) {
typeInfo.hasErrors = true;
if (isImport) {
error(builder, JavaErrorMessages.message("import.statement.identifier.or.asterisk.expected."));
refElement.drop();
return prevElement;
}
else {
error(builder, JavaErrorMessages.message("expected.identifier"));
emptyElement(builder, JavaElementType.REFERENCE_PARAMETER_LIST);
break;
}
}
if (parameterList) {
typeInfo.isParameterized = parseReferenceParameterList(builder, true, diamonds);
}
else {
emptyElement(builder, JavaElementType.REFERENCE_PARAMETER_LIST);
}
}
refElement.done(isStaticImport ? JavaElementType.IMPORT_STATIC_REFERENCE : JavaElementType.JAVA_CODE_REFERENCE);
return refElement;
}
public boolean parseReferenceParameterList(final PsiBuilder builder, final boolean wildcard, final boolean diamonds) {
final PsiBuilder.Marker list = builder.mark();
if (!expect(builder, JavaTokenType.LT)) {
list.done(JavaElementType.REFERENCE_PARAMETER_LIST);
return false;
}
int flags = set(set(EAT_LAST_DOT, WILDCARD, wildcard), DIAMONDS, diamonds);
boolean isOk = true;
while (true) {
if (parseTypeInfo(builder, flags, true) == null) {
error(builder, JavaErrorMessages.message("expected.identifier"));
}
else {
IElementType tokenType = builder.getTokenType();
if (WILDCARD_KEYWORD_SET.contains(tokenType)) {
parseReferenceList(builder, tokenType, null, JavaTokenType.AND);
}
}
if (expect(builder, JavaTokenType.GT)) {
break;
}
else if (!expectOrError(builder, JavaTokenType.COMMA, "expected.gt.or.comma")) {
isOk = false;
break;
}
flags = set(flags, DIAMONDS, false);
}
list.done(JavaElementType.REFERENCE_PARAMETER_LIST);
return isOk;
}
@NotNull
public PsiBuilder.Marker parseTypeParameters(final PsiBuilder builder) {
final PsiBuilder.Marker list = builder.mark();
if (!expect(builder, JavaTokenType.LT)) {
list.done(JavaElementType.TYPE_PARAMETER_LIST);
return list;
}
while (true) {
final PsiBuilder.Marker param = parseTypeParameter(builder);
if (param == null) {
error(builder, JavaErrorMessages.message("expected.type.parameter"));
}
if (!expect(builder, JavaTokenType.COMMA)) {
break;
}
}
if (!expect(builder, JavaTokenType.GT)) {
// hack for completion
if (builder.getTokenType() == JavaTokenType.IDENTIFIER) {
if (builder.lookAhead(1) == JavaTokenType.GT) {
final PsiBuilder.Marker errorElement = builder.mark();
builder.advanceLexer();
errorElement.error(JavaErrorMessages.message("unexpected.identifier"));
builder.advanceLexer();
}
else {
error(builder, JavaErrorMessages.message("expected.gt"));
}
}
else {
error(builder, JavaErrorMessages.message("expected.gt"));
}
}
list.done(JavaElementType.TYPE_PARAMETER_LIST);
return list;
}
@Nullable
public PsiBuilder.Marker parseTypeParameter(final PsiBuilder builder) {
final PsiBuilder.Marker param = builder.mark();
myParser.getDeclarationParser().parseAnnotations(builder);
final boolean wild = expect(builder, JavaTokenType.QUEST);
if (!wild && !expect(builder, JavaTokenType.IDENTIFIER)) {
param.rollbackTo();
return null;
}
parseReferenceList(builder, JavaTokenType.EXTENDS_KEYWORD, JavaElementType.EXTENDS_BOUND_LIST, JavaTokenType.AND);
if (!wild) {
param.done(JavaElementType.TYPE_PARAMETER);
}
else {
param.error(JavaErrorMessages.message("wildcard.not.expected"));
}
return param;
}
@NotNull
public PsiBuilder.Marker parseReferenceList(final PsiBuilder builder, final IElementType start,
@Nullable final IElementType type, final IElementType delimiter) {
final PsiBuilder.Marker element = builder.mark();
if (expect(builder, start)) {
while (true) {
final PsiBuilder.Marker classReference = parseJavaCodeReference(builder, true, true, false, false);
if (classReference == null) {
error(builder, JavaErrorMessages.message("expected.identifier"));
}
if (!expect(builder, delimiter)) {
break;
}
}
}
if (type != null) {
element.done(type);
}
else {
element.error(JavaErrorMessages.message("bound.not.expected"));
}
return element;
}
}