/* * 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); } public static final WhitespacesAndCommentsBinder GREEDY_RIGHT_EDGE_PROCESSOR = new WhitespacesAndCommentsBinder() { @Override public int getEdgePosition(final List<IElementType> tokens, final boolean atStreamEdge, final TokenTextGetter getter) { return tokens.size(); } }; 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(); } }; } }