blob: ec6b39106d0b91696dcb98f8c5d88e4c0620124c [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.*;
import com.intellij.lang.impl.PsiBuilderAdapter;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.lang.java.JavaParserDefinition;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.indexing.IndexingDataKeys;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import java.util.List;
public class JavaParserUtil {
private static final Key<LanguageLevel> LANG_LEVEL_KEY = Key.create("JavaParserUtil.LanguageLevel");
private static final Key<Boolean> DEEP_PARSE_BLOCKS_IN_STATEMENTS = Key.create("JavaParserUtil.ParserExtender");
public interface ParserWrapper {
void parse(PsiBuilder builder);
}
private static class PrecedingWhitespacesAndCommentsBinder implements WhitespacesAndCommentsBinder {
private final boolean myAfterEmptyImport;
public PrecedingWhitespacesAndCommentsBinder(final boolean afterImport) {
this.myAfterEmptyImport = afterImport;
}
@Override
public int getEdgePosition(final List<IElementType> tokens, final boolean atStreamEdge, final TokenTextGetter
getter) {
if (tokens.size() == 0) return 0;
// 1. bind doc comment
for (int idx = tokens.size() - 1; idx >= 0; idx--) {
if (tokens.get(idx) == JavaDocElementType.DOC_COMMENT) return idx;
}
// 2. bind plain comments
int result = tokens.size();
for (int idx = tokens.size() - 1; idx >= 0; idx--) {
final IElementType tokenType = tokens.get(idx);
if (ElementType.JAVA_WHITESPACE_BIT_SET.contains(tokenType)) {
if (StringUtil.getLineBreakCount(getter.get(idx)) > 1) break;
}
else if (ElementType.JAVA_PLAIN_COMMENT_BIT_SET.contains(tokenType)) {
if (atStreamEdge ||
(idx == 0 && myAfterEmptyImport) ||
(idx > 0 && ElementType.JAVA_WHITESPACE_BIT_SET.contains(tokens.get(idx - 1)) && StringUtil.containsLineBreak(getter.get(idx - 1)))) {
result = idx;
}
}
else break;
}
return result;
}
}
private static class TrailingWhitespacesAndCommentsBinder implements WhitespacesAndCommentsBinder {
@Override
public int getEdgePosition(final List<IElementType> tokens, final boolean atStreamEdge, final TokenTextGetter getter) {
if (tokens.size() == 0) return 0;
int result = 0;
for (int idx = 0; idx < tokens.size(); idx++) {
final IElementType tokenType = tokens.get(idx);
if (ElementType.JAVA_WHITESPACE_BIT_SET.contains(tokenType)) {
if (StringUtil.containsLineBreak(getter.get(idx))) break;
}
else if (ElementType.JAVA_PLAIN_COMMENT_BIT_SET.contains(tokenType)) {
result = idx + 1;
}
else break;
}
return result;
}
}
private static final TokenSet PRECEDING_COMMENT_SET = ElementType.FULL_MEMBER_BIT_SET;
private static final TokenSet TRAILING_COMMENT_SET = TokenSet.orSet(
TokenSet.create(JavaElementType.PACKAGE_STATEMENT),
ElementType.IMPORT_STATEMENT_BASE_BIT_SET, ElementType.FULL_MEMBER_BIT_SET, ElementType.JAVA_STATEMENT_BIT_SET);
public static final WhitespacesAndCommentsBinder PRECEDING_COMMENT_BINDER = new PrecedingWhitespacesAndCommentsBinder(false);
public static final WhitespacesAndCommentsBinder SPECIAL_PRECEDING_COMMENT_BINDER = new PrecedingWhitespacesAndCommentsBinder(true);
public static final WhitespacesAndCommentsBinder TRAILING_COMMENT_BINDER = new TrailingWhitespacesAndCommentsBinder();
private JavaParserUtil() { }
public static void setLanguageLevel(final PsiBuilder builder, final LanguageLevel level) {
builder.putUserDataUnprotected(LANG_LEVEL_KEY, level);
}
@NotNull
public static LanguageLevel getLanguageLevel(final PsiBuilder builder) {
final LanguageLevel level = builder.getUserDataUnprotected(LANG_LEVEL_KEY);
assert level != null : builder;
return level;
}
public static void setParseStatementCodeBlocksDeep(final PsiBuilder builder, final boolean deep) {
builder.putUserDataUnprotected(DEEP_PARSE_BLOCKS_IN_STATEMENTS, deep);
}
public static boolean isParseStatementCodeBlocksDeep(final PsiBuilder builder) {
return Boolean.TRUE.equals(builder.getUserDataUnprotected(DEEP_PARSE_BLOCKS_IN_STATEMENTS));
}
@NotNull
public static PsiBuilder createBuilder(final ASTNode chameleon) {
final PsiElement psi = chameleon.getPsi();
assert psi != null : chameleon;
final Project project = psi.getProject();
CharSequence text;
if (TreeUtil.isCollapsedChameleon(chameleon)) {
text = chameleon.getChars();
}
else {
text = psi.getUserData(IndexingDataKeys.FILE_TEXT_CONTENT_KEY);
if (text == null) text = chameleon.getChars();
}
final PsiBuilderFactory factory = PsiBuilderFactory.getInstance();
final LanguageLevel level = PsiUtil.getLanguageLevel(psi);
final Lexer lexer = JavaParserDefinition.createLexer(level);
Language language = psi.getLanguage();
if (!language.isKindOf(JavaLanguage.INSTANCE)) language = JavaLanguage.INSTANCE;
final PsiBuilder builder = factory.createBuilder(project, chameleon, lexer, language, text);
setLanguageLevel(builder, level);
return builder;
}
@NotNull
public static PsiBuilder createBuilder(final LighterLazyParseableNode chameleon) {
final PsiElement psi = chameleon.getContainingFile();
assert psi != null : chameleon;
final Project project = psi.getProject();
final PsiBuilderFactory factory = PsiBuilderFactory.getInstance();
final LanguageLevel level = PsiUtil.getLanguageLevel(psi);
final Lexer lexer = JavaParserDefinition.createLexer(level);
final PsiBuilder builder = factory.createBuilder(project, chameleon, lexer, chameleon.getTokenType().getLanguage(), chameleon.getText());
setLanguageLevel(builder, level);
return builder;
}
@Nullable
public static ASTNode parseFragment(final ASTNode chameleon, final ParserWrapper wrapper) {
return parseFragment(chameleon, wrapper, true, LanguageLevel.HIGHEST);
}
@Nullable
public static ASTNode parseFragment(final ASTNode chameleon, final ParserWrapper wrapper, final boolean eatAll, final LanguageLevel level) {
final PsiElement psi = (chameleon.getTreeParent() != null ? chameleon.getTreeParent().getPsi() : chameleon.getPsi());
assert psi != null : chameleon;
final Project project = psi.getProject();
final PsiBuilderFactory factory = PsiBuilderFactory.getInstance();
final Lexer lexer = chameleon.getElementType() == JavaDocElementType.DOC_COMMENT
? JavaParserDefinition.createDocLexer(level) : JavaParserDefinition.createLexer(level);
final PsiBuilder builder = factory.createBuilder(project, chameleon, lexer, chameleon.getElementType().getLanguage(), chameleon.getChars());
setLanguageLevel(builder, level);
final PsiBuilder.Marker root = builder.mark();
wrapper.parse(builder);
if (!builder.eof()) {
if (!eatAll) throw new AssertionError("Unexpected tokens");
final PsiBuilder.Marker extras = builder.mark();
while (!builder.eof()) builder.advanceLexer();
extras.error(JavaErrorMessages.message("unexpected.tokens"));
}
root.done(chameleon.getElementType());
return builder.getTreeBuilt().getFirstChildNode();
}
public static void done(final PsiBuilder.Marker marker, final IElementType type) {
marker.done(type);
final WhitespacesAndCommentsBinder left = PRECEDING_COMMENT_SET.contains(type) ? PRECEDING_COMMENT_BINDER : null;
final WhitespacesAndCommentsBinder right = TRAILING_COMMENT_SET.contains(type) ? TRAILING_COMMENT_BINDER : null;
marker.setCustomEdgeTokenBinders(left, right);
}
@Nullable
public static IElementType exprType(@Nullable final PsiBuilder.Marker marker) {
return marker != null ? ((LighterASTNode)marker).getTokenType() : null;
}
// used instead of PsiBuilder.error() as it keeps all subsequent error messages
public static void error(final PsiBuilder builder, final String message) {
builder.mark().error(message);
}
public static void error(final PsiBuilder builder, final String message, @Nullable final PsiBuilder.Marker before) {
if (before == null) {
error(builder, message);
}
else {
before.precede().errorBefore(message, before);
}
}
public static boolean expectOrError(PsiBuilder builder, TokenSet expected, @PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String key) {
if (!PsiBuilderUtil.expect(builder, expected)) {
error(builder, JavaErrorMessages.message(key));
return false;
}
return true;
}
public static boolean expectOrError(PsiBuilder builder, IElementType expected, @PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String key) {
if (!PsiBuilderUtil.expect(builder, expected)) {
error(builder, JavaErrorMessages.message(key));
return false;
}
return true;
}
public static void emptyElement(final PsiBuilder builder, final IElementType type) {
builder.mark().done(type);
}
public static void emptyElement(final PsiBuilder.Marker before, final IElementType type) {
before.precede().doneBefore(type, before);
}
public static void semicolon(final PsiBuilder builder) {
expectOrError(builder, JavaTokenType.SEMICOLON, "expected.semicolon");
}
public static PsiBuilder braceMatchingBuilder(final PsiBuilder builder) {
final PsiBuilder.Marker pos = builder.mark();
int braceCount = 1;
while (!builder.eof()) {
final IElementType tokenType = builder.getTokenType();
if (tokenType == JavaTokenType.LBRACE) braceCount++;
else if (tokenType == JavaTokenType.RBRACE) braceCount--;
if (braceCount == 0) break;
builder.advanceLexer();
}
final int stopAt = builder.getCurrentOffset();
pos.rollbackTo();
return stoppingBuilder(builder, stopAt);
}
public static PsiBuilder stoppingBuilder(final PsiBuilder builder, final int stopAt) {
return new PsiBuilderAdapter(builder) {
@Override
public IElementType getTokenType() {
return getCurrentOffset() < stopAt ? super.getTokenType() : null;
}
@Override
public boolean eof() {
return getCurrentOffset() >= stopAt || super.eof();
}
};
}
public static PsiBuilder stoppingBuilder(final PsiBuilder builder, final Condition<Pair<IElementType, String>> condition) {
return new PsiBuilderAdapter(builder) {
@Override
public IElementType getTokenType() {
final Pair<IElementType, String> input = Pair.create(builder.getTokenType(), builder.getTokenText());
return condition.value(input) ? null : super.getTokenType();
}
@Override
public boolean eof() {
final Pair<IElementType, String> input = Pair.create(builder.getTokenType(), builder.getTokenText());
return condition.value(input) || super.eof();
}
};
}
}