/* * Copyright 2000-2012 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.psi.impl.source.codeStyle; import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.impl.source.SourceTreeToPsiMap; import com.intellij.psi.jsp.JavaJspRecursiveElementVisitor; import com.intellij.psi.jsp.JspFile; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; /** * @author max */ public class BraceEnforcer extends JavaJspRecursiveElementVisitor { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.BraceEnforcer"); private final PostFormatProcessorHelper myPostProcessor; public BraceEnforcer(CodeStyleSettings settings) { myPostProcessor = new PostFormatProcessorHelper(settings.getCommonSettings(JavaLanguage.INSTANCE)); } @Override public void visitReferenceExpression(PsiReferenceExpression expression) { visitElement(expression); } @Override public void visitIfStatement(PsiIfStatement statement) { if (checkElementContainsRange(statement)) { final SmartPsiElementPointer pointer = SmartPointerManager.getInstance(statement.getProject()).createSmartPsiElementPointer(statement); super.visitIfStatement(statement); statement = (PsiIfStatement)pointer.getElement(); if (statement == null) { return; } processStatement(statement, statement.getThenBranch(), myPostProcessor.getSettings().IF_BRACE_FORCE); final PsiStatement elseBranch = statement.getElseBranch(); if (!(elseBranch instanceof PsiIfStatement) || !myPostProcessor.getSettings().SPECIAL_ELSE_IF_TREATMENT) { processStatement(statement, elseBranch, myPostProcessor.getSettings().IF_BRACE_FORCE); } } } @Override public void visitForStatement(PsiForStatement statement) { if (checkElementContainsRange(statement)) { super.visitForStatement(statement); processStatement(statement, statement.getBody(), myPostProcessor.getSettings().FOR_BRACE_FORCE); } } @Override public void visitForeachStatement(PsiForeachStatement statement) { if (checkElementContainsRange(statement)) { super.visitForeachStatement(statement); processStatement(statement, statement.getBody(), myPostProcessor.getSettings().FOR_BRACE_FORCE); } } @Override public void visitWhileStatement(PsiWhileStatement statement) { if (checkElementContainsRange(statement)) { super.visitWhileStatement(statement); processStatement(statement, statement.getBody(), myPostProcessor.getSettings().WHILE_BRACE_FORCE); } } @Override public void visitDoWhileStatement(PsiDoWhileStatement statement) { if (checkElementContainsRange(statement)) { super.visitDoWhileStatement(statement); processStatement(statement, statement.getBody(), myPostProcessor.getSettings().DOWHILE_BRACE_FORCE); } } @Override public void visitJspFile(JspFile file) { final PsiClass javaRoot = file.getJavaClass(); if (javaRoot != null) { javaRoot.accept(this); } } private void processStatement(PsiStatement statement, PsiStatement blockCandidate, int options) { if (blockCandidate instanceof PsiBlockStatement || blockCandidate == null) return; if (options == CommonCodeStyleSettings.FORCE_BRACES_ALWAYS || (options == CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE && PostFormatProcessorHelper.isMultiline(statement))) { replaceWithBlock(statement, blockCandidate); } } private void replaceWithBlock(@NotNull PsiStatement statement, PsiStatement blockCandidate) { if (!statement.isValid()) { LOG.assertTrue(false); } if (!checkRangeContainsElement(blockCandidate)) return; final PsiManager manager = statement.getManager(); LOG.assertTrue(manager != null); final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory(); String oldText = blockCandidate.getText(); // There is a possible case that target block to wrap ends with single-line comment. Example: // if (true) i = 1; // Cool assignment // We can't just surround target block of code with curly braces because the closing one will be treated as comment as well. // Hence, we perform a check if we have such situation at the moment and insert new line before the closing brace. int lastLineFeedIndex = oldText.lastIndexOf("\n"); lastLineFeedIndex = Math.max(0, lastLineFeedIndex); int lastLineCommentIndex = oldText.indexOf("//", lastLineFeedIndex); StringBuilder buf = new StringBuilder(oldText.length() + 5); buf.append("{ ").append(oldText); if (lastLineCommentIndex >= 0) { buf.append("\n"); } buf.append(" }"); final int oldTextLength = statement.getTextLength(); try { CodeEditUtil.replaceChild(SourceTreeToPsiMap.psiElementToTree(statement), SourceTreeToPsiMap.psiElementToTree(blockCandidate), SourceTreeToPsiMap.psiElementToTree(factory.createStatementFromText(buf.toString(), null))); CodeStyleManager.getInstance(statement.getProject()).reformat(statement, true); } catch (IncorrectOperationException e) { LOG.error(e); } finally { updateResultRange(oldTextLength , statement.getTextLength()); } } protected void updateResultRange(final int oldTextLength, final int newTextLength) { myPostProcessor.updateResultRange(oldTextLength, newTextLength); } protected boolean checkElementContainsRange(final PsiElement element) { return myPostProcessor.isElementPartlyInRange(element); } protected boolean checkRangeContainsElement(final PsiElement element) { return myPostProcessor.isElementFullyInRange(element); } public PsiElement process(PsiElement formatted) { LOG.assertTrue(formatted.isValid()); formatted.accept(this); return formatted; } public TextRange processText(final PsiFile source, final TextRange rangeToReformat) { myPostProcessor.setResultTextRange(rangeToReformat); source.accept(this); return myPostProcessor.getResultTextRange(); } }