SSR: fix unmatched member replacement in Java

This commit is contained in:
Bas Leijdekkers
2016-10-13 10:15:39 +02:00
parent 72a2b8e8c4
commit 266bd8d709
11 changed files with 219 additions and 108 deletions
@@ -32,12 +32,10 @@ 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 com.siyeh.ig.psiutils.PsiElementOrderComparator;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* @author Eugene.Kudelevsky
@@ -222,10 +220,66 @@ public class JavaReplaceHandler extends StructuralReplaceHandler {
copyExtendsListIfNotReplaced(originalClass, queryClass, replacementClass);
copyImplementsListIfNotReplaced(originalClass, queryClass, replacementClass);
copyTypeParameterListIfNotReplaced(originalClass, queryClass, replacementClass);
copyUnmatchedMembers(originalClass, originalNamedElements, replacementClass);
}
}
}
private static void copyUnmatchedMembers(PsiClass originalClass,
Map<String, PsiNamedElement> originalNamedElements,
PsiClass replacementClass) {
final List<? extends PsiElement> elements = originalClass.getUserData(GlobalMatchingVisitor.UNMATCHED_ELEMENTS_KEY);
if (elements == null) {
return;
}
final List<PsiNamedElement> anchors = PsiTreeUtil.getChildrenOfTypeAsList(replacementClass, PsiNamedElement.class);
for (PsiNamedElement anchor : anchors) {
final String replacedMemberName = anchor.getName();
final PsiNamedElement originalMember = originalNamedElements.get(replacedMemberName);
if (originalMember == null) {
continue;
}
for (Iterator<? extends PsiElement> iterator = elements.iterator(); iterator.hasNext(); ) {
PsiElement element = iterator.next();
if (PsiElementOrderComparator.getInstance().compare(element, originalMember) < 0) {
addElementAndWhitespaceBeforeAnchor(replacementClass, element, anchor);
iterator.remove();
}
else {
break;
}
}
}
final PsiElement anchor = replacementClass.getRBrace();
if (anchor == null) {
return;
}
for (PsiElement element : elements) {
addElementAndWhitespaceBeforeAnchor(replacementClass, element, anchor);
}
}
private static void addElementAndWhitespaceBeforeAnchor(PsiClass replacementClass, PsiElement element, PsiElement anchor) {
final PsiElement replacementSibling = anchor.getPrevSibling();
if (replacementSibling instanceof PsiWhiteSpace) {
replacementSibling.delete();
}
final PsiElement prevSibling = element.getPrevSibling();
if (prevSibling instanceof PsiWhiteSpace || PsiUtil.isJavaToken(prevSibling, JavaTokenType.COMMA)) {
final PsiElement prevPrevSibling = prevSibling.getPrevSibling();
if (PsiUtil.isJavaToken(prevPrevSibling, JavaTokenType.COMMA)) {
replacementClass.addBefore(prevPrevSibling, anchor);
}
replacementClass.addBefore(prevSibling, anchor);
}
replacementClass.addBefore(element, anchor);
final PsiElement nextSibling = element.getNextSibling();
if (nextSibling instanceof PsiWhiteSpace) {
replacementClass.addBefore(nextSibling, anchor);
}
}
private static void copyMethodBodyIfNotReplaced(PsiMethod original, PsiMethod query, PsiMethod replacement) {
final PsiCodeBlock originalBody = original.getBody();
if (originalBody != null && query.getBody() == null && replacement.getBody() == null) {
@@ -328,7 +382,7 @@ public class JavaReplaceHandler extends StructuralReplaceHandler {
if (replacement instanceof PsiTryStatement) {
final PsiTryStatement tryStatement = (PsiTryStatement)replacement;
final List<PsiElement> unmatchedElements = elementToReplace.getUserData(GlobalMatchingVisitor.UNMATCHED_ELEMENTS_KEY);
final List<? extends PsiElement> unmatchedElements = elementToReplace.getUserData(GlobalMatchingVisitor.UNMATCHED_ELEMENTS_KEY);
if (unmatchedElements != null) {
final PsiElement firstElement = unmatchedElements.get(0);
if (firstElement instanceof PsiResourceList) addElementAfterAnchor(tryStatement, firstElement, tryStatement.getFirstChild());
@@ -18,7 +18,6 @@ package com.intellij.structuralsearch;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.template.JavaCodeContextType;
import com.intellij.codeInsight.template.TemplateContextType;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.dupLocator.iterators.NodeIterator;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.Language;
@@ -33,6 +32,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.structuralsearch.impl.matcher.*;
import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor;
@@ -523,27 +523,12 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
@Override
public void provideAdditionalReplaceOptions(@NotNull PsiElement node, final ReplaceOptions options, final ReplacementBuilder builder) {
final String templateText = TemplateManager.getInstance(node.getProject()).createTemplate("", "", options.getReplacement()).getTemplateText();
node.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
visitElement(expression);
}
@Override
public void visitClass(PsiClass aClass) {
super.visitClass(aClass);
MatchVariableConstraint constraint =
options.getMatchOptions().getVariableConstraint(CompiledPattern.ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME);
if (constraint != null) {
ParameterInfo e = new ParameterInfo();
e.setName(CompiledPattern.ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME);
e.setStartIndex(templateText.lastIndexOf('}'));
builder.addParametrization(e);
}
}
@Override
public void visitParameter(PsiParameter parameter) {
super.visitParameter(parameter);
@@ -603,7 +588,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
final PsiElement parent = currentElement.getParent();
if (parent instanceof PsiVariable) {
final PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(parent, PsiWhiteSpace.class);
if (prevSibling instanceof PsiJavaToken && JavaTokenType.COMMA.equals(((PsiJavaToken)prevSibling).getTokenType())) {
if (PsiUtil.isJavaToken(prevSibling, JavaTokenType.COMMA)) {
buf.append(',');
}
}
@@ -635,7 +620,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
}
else if (parent instanceof PsiClass) {
final PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(currentElement, PsiWhiteSpace.class);
if (prevSibling instanceof PsiJavaToken && JavaTokenType.COMMA.equals(((PsiJavaToken)prevSibling).getTokenType())) {
if (PsiUtil.isJavaToken(prevSibling, JavaTokenType.COMMA)) {
buf.append(',');
}
else {
@@ -1,6 +1,20 @@
/*
* Copyright 2000-2016 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.structuralsearch.impl.matcher;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.structuralsearch.impl.matcher.strategies.ExprMatchingStrategy;
import org.jetbrains.annotations.Nullable;
@@ -63,8 +77,6 @@ public class JavaCompiledPattern extends CompiledPattern {
return null;
}
public static final Key<String> ALL_CLASS_CONTENT_VAR_NAME_KEY = Key.create("AllClassContent");
public boolean isRequestsSuperFields() {
return requestsSuperFields;
}
@@ -426,37 +426,43 @@ public class JavaMatchingVisitor extends JavaElementVisitor {
assert pattern instanceof JavaCompiledPattern;
final JavaCompiledPattern javaPattern = (JavaCompiledPattern)pattern;
final String unmatchedHandlerName = clazz.getUserData(JavaCompiledPattern.ALL_CLASS_CONTENT_VAR_NAME_KEY);
final MatchingHandler allRemainingClassContentElementHandler = unmatchedHandlerName != null ? pattern.getHandler(unmatchedHandlerName) : null;
MatchContext.MatchedElementsListener newListener = null;
MatchContext.MatchedElementsListener listener = new MatchContext.MatchedElementsListener() {
private Set<PsiElement> myMatchedElements;
if (allRemainingClassContentElementHandler != null) {
myMatchingVisitor.getMatchContext().setMatchedElementsListener(
newListener = new MatchContext.MatchedElementsListener() {
private Set<PsiElement> myMatchedElements;
@Override
public void matchedElements(Collection<PsiElement> matchedElements) {
if (matchedElements == null) return;
if (myMatchedElements == null) {
myMatchedElements = new HashSet<>(matchedElements);
}
else {
myMatchedElements.addAll(matchedElements);
}
}
public void matchedElements(Collection<PsiElement> matchedElements) {
if (matchedElements == null) return;
if (myMatchedElements == null) {
myMatchedElements = new HashSet<>(matchedElements);
}
else {
myMatchedElements.addAll(matchedElements);
}
}
public void commitUnmatched() {
final SubstitutionHandler handler = (SubstitutionHandler)allRemainingClassContentElementHandler;
for (PsiElement el = clazz2.getFirstChild(); el != null; el = el.getNextSibling()) {
if (el instanceof PsiMember && (myMatchedElements == null || !myMatchedElements.contains(el))) {
handler.handle(el, myMatchingVisitor.getMatchContext());
}
}
@Override
public void commitUnmatched() {
final List<PsiMember> members = PsiTreeUtil.getChildrenOfTypeAsList(clazz2, PsiMember.class);
final List<PsiMember> unmatchedElements =
ContainerUtil.filter(members, a -> myMatchedElements == null || !myMatchedElements.contains(a));
MatchingHandler unmatchedSubstitutionHandler = null;
for (PsiElement element = clazz.getFirstChild(); element != null; element = element.getNextSibling()) {
if (element instanceof PsiTypeElement && element.getNextSibling() instanceof PsiErrorElement) {
unmatchedSubstitutionHandler = pattern.getHandler(element);
break;
}
}
);
}
if (unmatchedSubstitutionHandler instanceof SubstitutionHandler) {
final SubstitutionHandler handler = (SubstitutionHandler)unmatchedSubstitutionHandler;
for (PsiMember element : unmatchedElements) {
handler.handle(element, myMatchingVisitor.getMatchContext());
}
} else {
clazz2.putUserData(GlobalMatchingVisitor.UNMATCHED_ELEMENTS_KEY, unmatchedElements);
}
}
};
myMatchingVisitor.getMatchContext().setMatchedElementsListener(listener);
boolean result = false;
try {
@@ -534,10 +540,10 @@ public class JavaMatchingVisitor extends JavaElementVisitor {
}
result = true;
return result;
return true;
}
finally {
if (result && newListener != null) newListener.commitUnmatched();
if (result) listener.commitUnmatched();
this.myClazz = saveClazz;
myMatchingVisitor.getMatchContext().setMatchedElementsListener(oldListener);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -24,14 +24,19 @@ import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.structuralsearch.*;
import com.intellij.structuralsearch.MalformedPatternException;
import com.intellij.structuralsearch.SSRBundle;
import com.intellij.structuralsearch.UnsupportedPatternException;
import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
import com.intellij.structuralsearch.impl.matcher.JavaCompiledPattern;
import com.intellij.structuralsearch.impl.matcher.filters.*;
import com.intellij.structuralsearch.impl.matcher.handlers.*;
import com.intellij.structuralsearch.impl.matcher.iterators.DocValuesIterator;
import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate;
import com.intellij.structuralsearch.impl.matcher.strategies.*;
import com.intellij.structuralsearch.impl.matcher.strategies.CommentMatchingStrategy;
import com.intellij.structuralsearch.impl.matcher.strategies.ExprMatchingStrategy;
import com.intellij.structuralsearch.impl.matcher.strategies.JavaDocMatchingStrategy;
import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
@@ -348,32 +353,6 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
handleReferenceText(psiClass.getName(), myCompilingVisitor.getContext());
GlobalCompilingVisitor.setFilter(handler, ClassFilter.getInstance());
boolean hasSubstitutionHandler = false;
for (PsiElement element = psiClass.getFirstChild(); element != null; element = element.getNextSibling()) {
if (element instanceof PsiTypeElement && element.getNextSibling() instanceof PsiErrorElement) {
// found match that
MatchingHandler unmatchedSubstitutionHandler = pattern.getHandler(element);
if (unmatchedSubstitutionHandler != null) {
psiClass.putUserData(JavaCompiledPattern.ALL_CLASS_CONTENT_VAR_NAME_KEY, pattern.getTypedVarString(element));
hasSubstitutionHandler = true;
}
}
}
if (!hasSubstitutionHandler) {
String name = CompiledPattern.ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME;
psiClass.putUserData(JavaCompiledPattern.ALL_CLASS_CONTENT_VAR_NAME_KEY, name);
MatchOptions options = myCompilingVisitor.getContext().getOptions();
if (pattern.getHandler(name) == null) {
pattern.createSubstitutionHandler(name, name, false, 0, Integer.MAX_VALUE, true);
MatchVariableConstraint constraint = new MatchVariableConstraint(true);
constraint.setName(name);
constraint.setMinCount(0);
constraint.setMaxCount(Integer.MAX_VALUE);
options.addVariableConstraint(constraint);
}
}
}
private SubstitutionHandler createAndSetSubstitutionHandlerFromReference(final PsiElement expr, final String referenceText,
@@ -398,14 +377,10 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
final PsiElement reference = expr.getFirstChild();
MatchingHandler referenceHandler = myCompilingVisitor.getContext().getPattern().getHandler(reference);
if (referenceHandler instanceof SubstitutionHandler &&
(reference instanceof PsiReferenceExpression)
) {
if (referenceHandler instanceof SubstitutionHandler && (reference instanceof PsiReferenceExpression)) {
// symbol
myCompilingVisitor.getContext().getPattern().setHandler(expr, referenceHandler);
referenceHandler.setFilter(
SymbolNodeFilter.getInstance()
);
referenceHandler.setFilter(SymbolNodeFilter.getInstance());
myCompilingVisitor.setHandler(expr, new SymbolHandler((SubstitutionHandler)referenceHandler));
}
@@ -582,7 +557,7 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() - 1);
}
private MatchingStrategy findStrategy(PsiElement el) {
private static MatchingStrategy findStrategy(PsiElement el) {
if (el instanceof PsiDocComment) {
return JavaDocMatchingStrategy.getInstance();
}
@@ -1,3 +1,18 @@
/*
* Copyright 2000-2016 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.structuralsearch.impl.matcher;
import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
@@ -22,7 +37,6 @@ import java.util.List;
* Class to hold compiled pattern information
*/
public abstract class CompiledPattern {
public static final String ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME = "__class_unmatched__";
private SearchScope scope;
private NodeIterator nodes;
private MatchingStrategy strategy;
@@ -1,3 +1,18 @@
/*
* Copyright 2000-2016 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.structuralsearch.impl.matcher;
import com.intellij.dupLocator.AbstractMatchingVisitor;
@@ -30,7 +45,7 @@ import java.util.Map;
@SuppressWarnings({"RefusedBequest"})
public class GlobalMatchingVisitor extends AbstractMatchingVisitor {
private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor");
public static final Key<List<PsiElement>> UNMATCHED_ELEMENTS_KEY = Key.create("UnmatchedElements");
public static final Key<List<? extends PsiElement>> UNMATCHED_ELEMENTS_KEY = Key.create("UnmatchedElements");
// the pattern element for visitor check
private PsiElement myElement;
@@ -274,7 +274,6 @@ class EditVarConstraintsDialog extends DialogWrapper {
public void actionPerformed(@NotNull final ActionEvent e) {
final List<String> variableNames = ContainerUtil.newArrayList(myConfiguration.getMatchOptions().getVariableConstraintNames());
variableNames.add(ScriptLog.SCRIPT_LOG_VAR_NAME);
variableNames.remove(CompiledPattern.ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME);
final EditScriptDialog dialog = new EditScriptDialog(project, customScriptCode.getChildComponent().getText(), variableNames);
dialog.show();
if (dialog.getExitCode() == OK_EXIT_CODE) {
@@ -789,7 +789,6 @@ public class SearchDialog extends DialogWrapper {
variableNames.add(variable.getName());
}
variableNames.add(Configuration.CONTEXT_VAR_NAME);
variableNames.add(CompiledPattern.ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME);
configuration.getMatchOptions().retainVariableConstraints(variableNames);
}
@@ -8,7 +8,6 @@ package somepackage;
* Title: ForecastSummaryReportTest
*/
public class ForecastSummaryReportTest extends SOTestCase {
ForecastSummaryReport oReport = null;
ScheduleGroupData oSkdgrp = null;
SODate oStart = null;
@@ -79,4 +78,5 @@ public class ForecastSummaryReportTest extends SOTestCase {
oReport.refresh();
assertNotNull(oReport);
}
}
@@ -1614,21 +1614,48 @@ public class StructuralReplaceTest extends StructuralReplaceTestCase {
}
public void testDontRequireSpecialVarsForUnmatchedContent() {
String actualResult;
String s43 = "public @Deprecated class Foo implements Comparable<Foo> {\n int x;\n void m(){}\n }";
String s43 = "public @Deprecated class Foo implements Comparable<Foo> {\n" +
" int x;\n" +
" void m(){}\n" +
" }";
String s44 = "class 'Class implements '_Interface {}";
String s45 = "@MyAnnotation\n" +
"class $Class$ implements $Interface$ {}";
String expectedResult16 = "@MyAnnotation public @Deprecated\n" +
"class Foo implements Comparable<Foo> {int x;\nvoid m(){}}";
"class Foo implements Comparable<Foo> {\n" +
" int x;\n" +
" void m(){}\n" +
" }";
actualResult = replacer.testReplace(s43,s44,s45,options, true);
assertEquals(
"Preserving class modifiers and generic information in type during replacement",
expectedResult16,
actualResult
replacer.testReplace(s43, s44, s45, options, true)
);
String in = "public class A {\n" +
" int i,j, k;\n" +
" void m1() {}\n" +
"\n" +
" public void m2() {}\n" +
" void m3() {}\n" +
"}";
String what = "class '_A {\n" +
" public void '_m();\n" +
"}";
String by = "class $A$ {\n" +
"\tprivate void $m$() {}\n" +
"}";
assertEquals("Should keep member order when replacing",
"public class A {\n" +
" int i ,j , k;\n" +
" void m1() {}\n" +
"\n" +
" private void m2() {}\n" +
" void m3() {}\n" +
"}",
replacer.testReplace(in, what, by, options));
}
public void _testClassReplacement2() {
@@ -1661,7 +1688,7 @@ public class StructuralReplaceTest extends StructuralReplaceTestCase {
String expectedResult15 = "class A {\n" +
" \n" +
" /* special comment*/\n" +
" private List<String> a = buildaMap();\n" +
" private List<String> a = buildaMap();\n" +
" private static List<String> buildaMap() {\n" +
" List<String> a = new ArrayList();\n" +
" int a = 1;\n" +
@@ -2472,18 +2499,43 @@ public class StructuralReplaceTest extends StructuralReplaceTestCase {
}
public void testReplaceInnerClass() {
String in = "public class A {" +
" public class B<T> extends A implements Serializable {}" +
String in = "public class A {\n" +
" public class B<T> extends A implements java.io.Serializable {}\n" +
"}";
String what = "class '_A {" +
" class '_B {}" +
"}";
String by = "class $A$ {" +
" private class $B$ {}" +
String by = "class $A$ {\n" +
" private class $B$ {\n" +
" }\n" +
"}";
assertEquals("public class A {" +
" private class B<T> extends A implements Serializable {}" +
assertEquals("public class A {\n" +
" private class B<T> extends A implements java.io.Serializable {\n" +
" }\n" +
"}",
replacer.testReplace(in, what, by, options));
String in2 = "public class A {\n" +
" void m1() {}\n" +
" public void m2() {}\n" +
" public class B<T> extends A implements java.io.Serializable {\n" +
" int zero() {\n" +
" return 0;\n" +
" }\n" +
" }\n" +
" void m3() {}\n" +
"}";
assertEquals("should replace unmatched class content correctly",
"public class A {\n" +
" void m1() {}\n" +
" public void m2() {}\n" +
" private class B<T> extends A implements java.io.Serializable {\n" +
" int zero() {\n" +
" return 0;\n" +
" }\n" +
" }\n" +
" void m3() {}\n" +
"}",
replacer.testReplace(in2, what, by, options));
}
}