/* * 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.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 not 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 { final IElementType tokenType = builder.getTokenType(); if (WILDCARD_KEYWORD_SET.contains(tokenType) && tokenType != null) { 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; } }