diff options
Diffstat (limited to 'java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java')
-rw-r--r-- | java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java new file mode 100644 index 000000000000..562c8aee753c --- /dev/null +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java @@ -0,0 +1,557 @@ +package com.intellij.structuralsearch; + +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.xml.XmlText; +import com.intellij.structuralsearch.impl.matcher.JavaMatchingVisitor; +import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil; +import com.intellij.structuralsearch.impl.matcher.PatternTreeContext; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; +import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext; +import com.intellij.structuralsearch.plugin.replace.impl.Replacer; +import com.intellij.structuralsearch.plugin.replace.impl.ReplacerUtil; +import com.intellij.util.IncorrectOperationException; +import com.siyeh.ig.psiutils.ImportUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Eugene.Kudelevsky + */ +public class JavaReplaceHandler extends StructuralReplaceHandler { + private final ReplacementContext myContext; + private PsiCodeBlock codeBlock; + + public JavaReplaceHandler(ReplacementContext context) { + this.myContext = context; + } + + private PsiCodeBlock getCodeBlock() throws IncorrectOperationException { + if (codeBlock == null) { + codeBlock = (PsiCodeBlock)MatcherImplUtil.createTreeFromText( + myContext.getOptions().getMatchOptions().getSearchPattern(), + PatternTreeContext.Block, + myContext.getOptions().getMatchOptions().getFileType(), + myContext.getProject() + )[0].getParent(); + } + return codeBlock; + } + + private static PsiElement findRealSubstitutionElement(PsiElement el) { + if (el instanceof PsiIdentifier) { + // matches are tokens, identifiers, etc + el = el.getParent(); + } + + if (el instanceof PsiReferenceExpression && + el.getParent() instanceof PsiMethodCallExpression + ) { + // method + el = el.getParent(); + } + + if (el instanceof PsiDeclarationStatement && ((PsiDeclarationStatement)el).getDeclaredElements()[0] instanceof PsiClass) { + el = ((PsiDeclarationStatement)el).getDeclaredElements()[0]; + } + return el; + } + + private static boolean isListContext(PsiElement el) { + boolean listContext = false; + final PsiElement parent = el.getParent(); + + if (parent instanceof PsiParameterList || + parent instanceof PsiExpressionList || + parent instanceof PsiCodeBlock || + parent instanceof PsiClass || + parent instanceof XmlText || + (parent instanceof PsiIfStatement && + (((PsiIfStatement)parent).getThenBranch() == el || + ((PsiIfStatement)parent).getElseBranch() == el + ) + ) || + (parent instanceof PsiLoopStatement && + ((PsiLoopStatement)parent).getBody() == el + ) + ) { + listContext = true; + } + + return listContext; + } + + @Nullable + private PsiNamedElement getSymbolReplacementTarget(final PsiElement el) + throws IncorrectOperationException { + if (myContext.getOptions().getMatchOptions().getFileType() != StdFileTypes.JAVA) return null; //? + final PsiStatement[] searchStatements = getCodeBlock().getStatements(); + if (searchStatements.length > 0 && + searchStatements[0] instanceof PsiExpressionStatement) { + final PsiExpression expression = ((PsiExpressionStatement)searchStatements[0]).getExpression(); + + if (expression instanceof PsiReferenceExpression && + ((PsiReferenceExpression)expression).getQualifierExpression() == null + ) { + // looks like symbol replacements, namely replace AAA by BBB, so lets do the best + if (el instanceof PsiNamedElement) { + return (PsiNamedElement)el; + } + } + } + + return null; + } + + private static PsiElement getMatchExpr(PsiElement replacement, PsiElement elementToReplace) { + if (replacement instanceof PsiExpressionStatement && + !(replacement.getLastChild() instanceof PsiJavaToken) && + !(replacement.getLastChild() instanceof PsiComment) + ) { + // replacement is expression (and pattern should be so) + // assert ... + replacement = ((PsiExpressionStatement)replacement).getExpression(); + } + else if (replacement instanceof PsiDeclarationStatement && + ((PsiDeclarationStatement)replacement).getDeclaredElements().length == 1 + ) { + return ((PsiDeclarationStatement)replacement).getDeclaredElements()[0]; + } + else if (replacement instanceof PsiBlockStatement && + elementToReplace instanceof PsiCodeBlock + ) { + return ((PsiBlockStatement)replacement).getCodeBlock(); + } + + return replacement; + } + + private boolean isSymbolReplacement(final PsiElement el) throws IncorrectOperationException { + return getSymbolReplacementTarget(el) != null; + } + + @SuppressWarnings({"unchecked", "ConstantConditions"}) + private void handleModifierList(final PsiElement el, final PsiElement replacement) throws IncorrectOperationException { + // We want to copy all comments, including doc comments and modifier lists + // that are present in matched nodes but not present in search/replace + + Map<String, String> newNameToSearchPatternNameMap = myContext.getNewName2PatternNameMap(); + + ModifierListOwnerCollector collector = new ModifierListOwnerCollector(); + el.accept(collector); + Map<String, PsiNamedElement> originalNamedElements = (Map<String, PsiNamedElement>)collector.namedElements.clone(); + collector.namedElements.clear(); + + replacement.accept(collector); + Map<String, PsiNamedElement> replacedNamedElements = (Map<String, PsiNamedElement>)collector.namedElements.clone(); + collector.namedElements.clear(); + + if (originalNamedElements.size() == 0 && replacedNamedElements.size() == 0) { + Replacer.handleComments(el, replacement, myContext); + return; + } + + final PsiStatement[] statements = getCodeBlock().getStatements(); + if (statements.length > 0) { + statements[0].getParent().accept(collector); + } + + Map<String, PsiNamedElement> searchedNamedElements = (Map<String, PsiNamedElement>)collector.namedElements.clone(); + collector.namedElements.clear(); + + for (String name : originalNamedElements.keySet()) { + PsiNamedElement originalNamedElement = originalNamedElements.get(name); + PsiNamedElement replacementNamedElement = replacedNamedElements.get(name); + String key = newNameToSearchPatternNameMap.get(name); + if (key == null) key = name; + PsiNamedElement searchNamedElement = searchedNamedElements.get(key); + + if (replacementNamedElement == null && originalNamedElements.size() == 1 && replacedNamedElements.size() == 1) { + replacementNamedElement = replacedNamedElements.entrySet().iterator().next().getValue(); + } + + PsiElement comment = null; + + if (originalNamedElement instanceof PsiDocCommentOwner) { + comment = ((PsiDocCommentOwner)originalNamedElement).getDocComment(); + if (comment == null) { + PsiElement prevElement = originalNamedElement.getPrevSibling(); + if (prevElement instanceof PsiWhiteSpace) { + prevElement = prevElement.getPrevSibling(); + } + if (prevElement instanceof PsiComment) { + comment = prevElement; + } + } + } + + if (replacementNamedElement != null && searchNamedElement != null) { + Replacer.handleComments(originalNamedElement, replacementNamedElement, myContext); + } + + if (comment != null && replacementNamedElement instanceof PsiDocCommentOwner && + !(replacementNamedElement.getFirstChild() instanceof PsiDocComment) + ) { + final PsiElement nextSibling = comment.getNextSibling(); + PsiElement prevSibling = comment.getPrevSibling(); + replacementNamedElement.addRangeBefore( + prevSibling instanceof PsiWhiteSpace ? prevSibling : comment, + nextSibling instanceof PsiWhiteSpace ? nextSibling : comment, + replacementNamedElement.getFirstChild() + ); + } + + if (originalNamedElement instanceof PsiModifierListOwner && + replacementNamedElement instanceof PsiModifierListOwner + ) { + PsiModifierList modifierList = ((PsiModifierListOwner)originalNamedElements.get(name)).getModifierList(); + + if (searchNamedElement instanceof PsiModifierListOwner) { + PsiModifierList modifierListOfSearchedElement = ((PsiModifierListOwner)searchNamedElement).getModifierList(); + final PsiModifierListOwner modifierListOwner = ((PsiModifierListOwner)replacementNamedElement); + PsiModifierList modifierListOfReplacement = modifierListOwner.getModifierList(); + + if (modifierListOfSearchedElement.getTextLength() == 0 && + modifierListOfReplacement.getTextLength() == 0 && + modifierList.getTextLength() > 0 + ) { + PsiElement space = modifierList.getNextSibling(); + if (!(space instanceof PsiWhiteSpace)) { + space = createWhiteSpace(space); + } + + modifierListOfReplacement.replace(modifierList); + // copy space after modifier list + if (space instanceof PsiWhiteSpace) { + modifierListOwner.addRangeAfter(space, space, modifierListOwner.getModifierList()); + } + } else if (modifierListOfSearchedElement.getTextLength() == 0 && modifierList.getTextLength() > 0) { + modifierListOfReplacement.addRange(modifierList.getFirstChild(), modifierList.getLastChild()); + } + } + } + } + } + + private PsiElement handleSymbolReplacement(PsiElement replacement, final PsiElement el) throws IncorrectOperationException { + PsiNamedElement nameElement = getSymbolReplacementTarget(el); + if (nameElement != null) { + PsiElement oldReplacement = replacement; + replacement = el.copy(); + ((PsiNamedElement)replacement).setName(oldReplacement.getText()); + } + + return replacement; + } + + public void replace(final ReplacementInfo info, ReplaceOptions options) { + PsiElement elementToReplace = info.getMatch(0); + PsiElement elementParent = elementToReplace.getParent(); + String replacementToMake = info.getReplacement(); + Project project = myContext.getProject(); + PsiElement el = findRealSubstitutionElement(elementToReplace); + boolean listContext = isListContext(el); + + if (el instanceof PsiAnnotation && !StringUtil.startsWithChar(replacementToMake, '@')) { + replacementToMake = "@" + replacementToMake; + } + + PsiElement[] statements = ReplacerUtil + .createTreeForReplacement(replacementToMake, el instanceof PsiMember && !isSymbolReplacement(el) ? + PatternTreeContext.Class : + PatternTreeContext.Block, myContext); + + if (listContext) { + if (statements.length > 1) { + elementParent.addRangeBefore(statements[0], statements[statements.length - 1], elementToReplace); + } + else if (statements.length == 1) { + PsiElement replacement = getMatchExpr(statements[0], elementToReplace); + + handleModifierList(el, replacement); + replacement = handleSymbolReplacement(replacement, el); + + if (replacement instanceof PsiTryStatement) { + final List<PsiCatchSection> unmatchedCatchSections = el.getUserData(JavaMatchingVisitor.UNMATCHED_CATCH_SECTION_CONTENT_VAR_KEY); + final PsiCatchSection[] catches = ((PsiTryStatement)replacement).getCatchSections(); + + if (unmatchedCatchSections != null) { + for (int i = unmatchedCatchSections.size() - 1; i >= 0; --i) { + final PsiParameter parameter = unmatchedCatchSections.get(i).getParameter(); + final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); + final PsiCatchSection catchSection = elementFactory.createCatchSection(parameter.getType(), parameter.getName(), null); + + catchSection.getCatchBlock().replace( + unmatchedCatchSections.get(i).getCatchBlock() + ); + replacement.addAfter( + catchSection, catches[catches.length - 1] + ); + replacement.addBefore(createWhiteSpace(replacement), replacement.getLastChild()); + } + } + } + + try { + final PsiElement inserted = elementParent.addBefore(replacement, elementToReplace); + + if (replacement instanceof PsiComment && + (elementParent instanceof PsiIfStatement || + elementParent instanceof PsiLoopStatement + ) + ) { + elementParent.addAfter(createSemicolon(replacement), inserted); + } + } + catch (IncorrectOperationException e) { + elementToReplace.replace(replacement); + } + } + } + else if (statements.length > 0) { + PsiElement replacement = ReplacerUtil.copySpacesAndCommentsBefore(elementToReplace, statements, replacementToMake, elementParent); + + replacement = getMatchExpr(replacement, elementToReplace); + + if (replacement instanceof PsiStatement && + !(replacement.getLastChild() instanceof PsiJavaToken) && + !(replacement.getLastChild() instanceof PsiComment) + ) { + // assert w/o ; + final PsiElement prevLastChildInParent = replacement.getLastChild().getPrevSibling(); + + if (prevLastChildInParent != null) { + elementParent.addRangeBefore(replacement.getFirstChild(), prevLastChildInParent, el); + } + else { + elementParent.addBefore(replacement.getFirstChild(), el); + } + + el.getNode().getTreeParent().removeChild(el.getNode()); + } + else { + // preserve comments + handleModifierList(el, replacement); + + if (replacement instanceof PsiClass) { + // modifier list + final PsiStatement[] searchStatements = getCodeBlock().getStatements(); + if (searchStatements.length > 0 && + searchStatements[0] instanceof PsiDeclarationStatement && + ((PsiDeclarationStatement)searchStatements[0]).getDeclaredElements()[0] instanceof PsiClass + ) { + final PsiClass replaceClazz = (PsiClass)replacement; + final PsiClass queryClazz = (PsiClass)((PsiDeclarationStatement)searchStatements[0]).getDeclaredElements()[0]; + final PsiClass clazz = (PsiClass)el; + + if (replaceClazz.getExtendsList().getTextLength() == 0 && + queryClazz.getExtendsList().getTextLength() == 0 && + clazz.getExtendsList().getTextLength() != 0 + ) { + replaceClazz.addBefore(clazz.getExtendsList().getPrevSibling(), replaceClazz.getExtendsList()); // whitespace + replaceClazz.getExtendsList().addRange( + clazz.getExtendsList().getFirstChild(), clazz.getExtendsList().getLastChild() + ); + } + + if (replaceClazz.getImplementsList().getTextLength() == 0 && + queryClazz.getImplementsList().getTextLength() == 0 && + clazz.getImplementsList().getTextLength() != 0 + ) { + replaceClazz.addBefore(clazz.getImplementsList().getPrevSibling(), replaceClazz.getImplementsList()); // whitespace + replaceClazz.getImplementsList().addRange( + clazz.getImplementsList().getFirstChild(), + clazz.getImplementsList().getLastChild() + ); + } + + if (replaceClazz.getTypeParameterList().getTextLength() == 0 && + queryClazz.getTypeParameterList().getTextLength() == 0 && + clazz.getTypeParameterList().getTextLength() != 0 + ) { + // skip < and > + replaceClazz.getTypeParameterList().replace( + clazz.getTypeParameterList() + ); + } + } + } + + replacement = handleSymbolReplacement(replacement, el); + + el.replace(replacement); + } + } + else { + final PsiElement nextSibling = el.getNextSibling(); + el.delete(); + if (nextSibling instanceof PsiWhiteSpace && nextSibling.isValid()) { + nextSibling.delete(); + } + } + + if (listContext) { + final int matchSize = info.getMatchesCount(); + + for (int i = 0; i < matchSize; ++i) { + PsiElement matchElement = info.getMatch(i); + PsiElement element = findRealSubstitutionElement(matchElement); + + if (element == null) continue; + PsiElement firstToDelete = element; + PsiElement lastToDelete = element; + PsiElement prevSibling = element.getPrevSibling(); + PsiElement nextSibling = element.getNextSibling(); + + if (prevSibling instanceof PsiWhiteSpace) { + firstToDelete = prevSibling; + prevSibling = prevSibling != null ? prevSibling.getPrevSibling() : null; + } + else if (prevSibling == null && nextSibling instanceof PsiWhiteSpace) { + lastToDelete = nextSibling; + } + + if (nextSibling instanceof XmlText && i + 1 < matchSize) { + final PsiElement next = info.getMatch(i + 1); + if (next != null && next == nextSibling.getNextSibling()) { + lastToDelete = nextSibling; + } + } + + if (element instanceof PsiExpression) { + final PsiElement parent = element.getParent().getParent(); + if ((parent instanceof PsiCall || + parent instanceof PsiAnonymousClass + ) && + prevSibling instanceof PsiJavaToken && + ((PsiJavaToken)prevSibling).getTokenType() == JavaTokenType.COMMA + ) { + firstToDelete = prevSibling; + } + } + else if (element instanceof PsiParameter && + prevSibling instanceof PsiJavaToken && + ((PsiJavaToken)prevSibling).getTokenType() == JavaTokenType.COMMA + ) { + firstToDelete = prevSibling; + } + + element.getParent().deleteChildRange(firstToDelete, lastToDelete); + } + } + } + + @Override + public void postProcess(PsiElement affectedElement, ReplaceOptions options) { + if (!affectedElement.isValid()) { + return; + } + if (options.isToUseStaticImport()) { + shortenWithStaticImports(affectedElement, 0, affectedElement.getTextLength()); + } + if (options.isToShortenFQN()) { + final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(affectedElement.getProject()); + codeStyleManager.shortenClassReferences(affectedElement, 0, affectedElement.getTextLength()); + } + } + + private static void shortenWithStaticImports(PsiElement affectedElement, int startOffset, int endOffset) { + final int elementOffset = affectedElement.getTextOffset(); + final int finalStartOffset = startOffset + elementOffset; + final int finalEndOffset = endOffset + elementOffset; + final List<PsiReferenceExpression> references = new ArrayList<PsiReferenceExpression>(); + final JavaRecursiveElementVisitor collector = new JavaRecursiveElementVisitor() { + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + final int offset = expression.getTextOffset(); + if (offset > finalEndOffset) { + return; + } + super.visitReferenceExpression(expression); + if (offset + expression.getTextLength() < finalStartOffset) + if (expression.getQualifierExpression() == null) { + return; + } + references.add(expression); + } + }; + affectedElement.accept(collector); + for (PsiReferenceExpression expression : references) { + final PsiElement target = expression.resolve(); + if (!(target instanceof PsiMember)) { + continue; + } + final PsiMember member = (PsiMember)target; + final PsiClass containingClass = member.getContainingClass(); + if (containingClass == null) { + continue; + } + final String className = containingClass.getQualifiedName(); + if (className == null) { + continue; + } + final String name = member.getName(); + if (name == null) { + continue; + } + if (ImportUtils.addStaticImport(className, name, expression)) { + final PsiExpression qualifierExpression = expression.getQualifierExpression(); + if (qualifierExpression != null) { + qualifierExpression.delete(); + } + } + } + } + + @Nullable + private static PsiElement createSemicolon(final PsiElement space) throws IncorrectOperationException { + final PsiStatement text = JavaPsiFacade.getInstance(space.getProject()).getElementFactory().createStatementFromText(";", null); + return text.getFirstChild(); + } + + private static PsiElement createWhiteSpace(final PsiElement space) throws IncorrectOperationException { + return PsiParserFacade.SERVICE.getInstance(space.getProject()).createWhiteSpaceFromText(" "); + } + + private static class ModifierListOwnerCollector extends JavaRecursiveElementWalkingVisitor { + HashMap<String, PsiNamedElement> namedElements = new HashMap<String, PsiNamedElement>(1); + + @Override + public void visitClass(PsiClass aClass) { + if (aClass instanceof PsiAnonymousClass) return; + handleNamedElement(aClass); + } + + private void handleNamedElement(final PsiNamedElement named) { + String name = named.getName(); + + assert name != null; + + if (StructuralSearchUtil.isTypedVariable(name)) { + name = name.substring(1, name.length() - 1); + } + + if (!namedElements.containsKey(name)) namedElements.put(name, named); + named.acceptChildren(this); + } + + @Override + public void visitVariable(PsiVariable var) { + handleNamedElement(var); + } + + @Override + public void visitMethod(PsiMethod method) { + handleNamedElement(method); + } + } +} |