blob: acc59abd67daba69c7cfef6dd5de6ad8066a61d5 [file] [log] [blame]
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.lang.Language;
import com.intellij.lang.StdLanguages;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
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.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.structuralsearch.impl.matcher.*;
import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor;
import com.intellij.structuralsearch.impl.matcher.compiler.JavaCompilingVisitor;
import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
import com.intellij.structuralsearch.impl.matcher.filters.JavaLexicalNodesFilter;
import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
import com.intellij.structuralsearch.plugin.replace.impl.ParameterInfo;
import com.intellij.structuralsearch.plugin.replace.impl.ReplacementBuilder;
import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext;
import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
import com.intellij.structuralsearch.plugin.ui.Configuration;
import com.intellij.structuralsearch.plugin.ui.SearchContext;
import com.intellij.structuralsearch.plugin.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Eugene.Kudelevsky
*/
public class JavaStructuralSearchProfile extends StructuralSearchProfile {
private JavaLexicalNodesFilter myJavaLexicalNodesFilter;
public String getText(PsiElement match, int start,int end) {
if (match instanceof PsiIdentifier) {
PsiElement parent = match.getParent();
if (parent instanceof PsiJavaCodeReferenceElement && !(parent instanceof PsiExpression)) {
match = parent; // care about generic
}
}
final String matchText = match.getText();
if (start==0 && end==-1) return matchText;
return matchText.substring(start,end == -1? matchText.length():end);
}
public Class getElementContextByPsi(PsiElement element) {
if (element instanceof PsiIdentifier) {
element = element.getParent();
}
if (element instanceof PsiMember) {
return PsiMember.class;
} else {
return PsiExpression.class;
}
}
@NotNull
public String getTypedVarString(final PsiElement element) {
String text;
if (element instanceof PsiNamedElement) {
text = ((PsiNamedElement)element).getName();
}
else if (element instanceof PsiAnnotation) {
PsiJavaCodeReferenceElement referenceElement = ((PsiAnnotation)element).getNameReferenceElement();
text = referenceElement == null ? null : referenceElement.getQualifiedName();
}
else if (element instanceof PsiNameValuePair) {
text = ((PsiNameValuePair)element).getName();
}
else {
text = element.getText();
if (StringUtil.startsWithChar(text, '@')) {
text = text.substring(1);
}
if (StringUtil.endsWithChar(text, ';')) text = text.substring(0, text.length() - 1);
else if (element instanceof PsiExpressionStatement) {
int i = text.indexOf(';');
if (i != -1) text = text.substring(0, i);
}
}
if (text==null) text = element.getText();
return text;
}
@Override
public String getMeaningfulText(PsiElement element) {
if (element instanceof PsiReferenceExpression &&
((PsiReferenceExpression)element).getQualifierExpression() != null) {
final PsiElement resolve = ((PsiReferenceExpression)element).resolve();
if (resolve instanceof PsiClass) return element.getText();
final PsiElement referencedElement = ((PsiReferenceExpression)element).getReferenceNameElement();
String text = referencedElement != null ? referencedElement.getText() : "";
if (resolve == null && text.length() > 0 && Character.isUpperCase(text.charAt(0))) {
return element.getText();
}
return text;
}
return super.getMeaningfulText(element);
}
@Override
public PsiElement updateCurrentNode(PsiElement targetNode) {
if (targetNode instanceof PsiCodeBlock && ((PsiCodeBlock)targetNode).getStatements().length == 1) {
PsiElement targetNodeParent = targetNode.getParent();
if (targetNodeParent instanceof PsiBlockStatement) {
targetNodeParent = targetNodeParent.getParent();
}
if (targetNodeParent instanceof PsiIfStatement || targetNodeParent instanceof PsiLoopStatement) {
targetNode = targetNodeParent;
}
}
return targetNode;
}
@Override
public PsiElement extendMatchedByDownUp(PsiElement targetNode) {
if (targetNode instanceof PsiIdentifier) {
targetNode = targetNode.getParent();
final PsiElement parent = targetNode.getParent();
if (parent instanceof PsiTypeElement || parent instanceof PsiStatement) targetNode = parent;
}
return targetNode;
}
@Override
public PsiElement extendMatchOnePsiFile(PsiElement file) {
if (file instanceof PsiIdentifier) {
// Searching in previous results
file = file.getParent();
}
return file;
}
public void compile(PsiElement[] elements, @NotNull GlobalCompilingVisitor globalVisitor) {
elements[0].getParent().accept(new JavaCompilingVisitor(globalVisitor));
}
@NotNull
public PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor) {
return new JavaMatchingVisitor(globalVisitor);
}
@NotNull
@Override
public PsiElementVisitor getLexicalNodesFilter(@NotNull LexicalNodesFilter filter) {
if (myJavaLexicalNodesFilter == null) {
myJavaLexicalNodesFilter = new JavaLexicalNodesFilter(filter);
}
return myJavaLexicalNodesFilter;
}
@NotNull
public CompiledPattern createCompiledPattern() {
return new JavaCompiledPattern();
}
@Override
public boolean canProcess(@NotNull FileType fileType) {
return fileType == StdFileTypes.JAVA;
}
public boolean isMyLanguage(@NotNull Language language) {
return language == StdLanguages.JAVA;
}
@Override
public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) {
return new JavaReplaceHandler(context);
}
@NotNull
@Override
public PsiElement[] createPatternTree(@NotNull String text,
@NotNull PatternTreeContext context,
@NotNull FileType fileType,
@Nullable Language language,
String contextName, @Nullable String extension,
@NotNull Project project,
boolean physical) {
if (physical) {
throw new UnsupportedOperationException(getClass() + " cannot create physical PSI");
}
PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
if (context == PatternTreeContext.Block) {
PsiElement element = elementFactory.createStatementFromText("{\n" + text + "\n}", null);
final PsiElement[] children = ((PsiBlockStatement)element).getCodeBlock().getChildren();
final int extraChildCount = 4;
if (children.length > extraChildCount) {
PsiElement[] result = new PsiElement[children.length - extraChildCount];
final int extraChildStart = 2;
System.arraycopy(children, extraChildStart, result, 0, children.length - extraChildCount);
return result;
}
else {
return PsiElement.EMPTY_ARRAY;
}
}
else if (context == PatternTreeContext.Class) {
PsiElement element = elementFactory.createStatementFromText("class A {\n" + text + "\n}", null);
PsiClass clazz = (PsiClass)((PsiDeclarationStatement)element).getDeclaredElements()[0];
PsiElement startChild = clazz.getLBrace();
if (startChild != null) startChild = startChild.getNextSibling();
PsiElement endChild = clazz.getRBrace();
if (endChild != null) endChild = endChild.getPrevSibling();
if (startChild == endChild) return PsiElement.EMPTY_ARRAY; // nothing produced
final List<PsiElement> result = new ArrayList<PsiElement>(3);
assert startChild != null;
for (PsiElement el = startChild.getNextSibling(); el != endChild && el != null; el = el.getNextSibling()) {
if (el instanceof PsiErrorElement) continue;
result.add(el);
}
return PsiUtilCore.toPsiElementArray(result);
}
else {
return PsiFileFactory.getInstance(project).createFileFromText("__dummy.java", text).getChildren();
}
}
@NotNull
@Override
public Editor createEditor(@NotNull SearchContext searchContext,
@NotNull FileType fileType,
Language dialect,
String text,
boolean useLastConfiguration) {
// provides autocompletion
PsiElement element = searchContext.getFile();
if (element != null && !useLastConfiguration) {
final Editor selectedEditor = FileEditorManager.getInstance(searchContext.getProject()).getSelectedTextEditor();
if (selectedEditor != null) {
int caretPosition = selectedEditor.getCaretModel().getOffset();
PsiElement positionedElement = searchContext.getFile().findElementAt(caretPosition);
if (positionedElement == null) {
positionedElement = searchContext.getFile().findElementAt(caretPosition + 1);
}
if (positionedElement != null) {
element = PsiTreeUtil.getParentOfType(
positionedElement,
PsiClass.class, PsiCodeBlock.class
);
}
}
}
final PsiManager psimanager = PsiManager.getInstance(searchContext.getProject());
final Project project = psimanager.getProject();
final PsiCodeFragment file = createCodeFragment(project, text, element);
final Document doc = PsiDocumentManager.getInstance(searchContext.getProject()).getDocument(file);
DaemonCodeAnalyzer.getInstance(searchContext.getProject()).setHighlightingEnabled(file, false);
return UIUtil.createEditor(doc, searchContext.getProject(), true, true, getTemplateContextType());
}
@Override
public Class<? extends TemplateContextType> getTemplateContextTypeClass() {
return JavaCodeContextType.class;
}
public PsiCodeFragment createCodeFragment(Project project, String text, PsiElement context) {
final JavaCodeFragmentFactory factory = JavaCodeFragmentFactory.getInstance(project);
return factory.createCodeBlockCodeFragment(text, context, true);
}
@Override
public void checkSearchPattern(Project project, MatchOptions options) {
class ValidatingVisitor extends JavaRecursiveElementWalkingVisitor {
private PsiElement myCurrent;
@Override public void visitAnnotation(PsiAnnotation annotation) {
final PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
if (nameReferenceElement == null ||
!nameReferenceElement.getText().equals(MatchOptions.MODIFIER_ANNOTATION_NAME)) {
return;
}
for(PsiNameValuePair pair:annotation.getParameterList().getAttributes()) {
final PsiAnnotationMemberValue value = pair.getValue();
if (value instanceof PsiArrayInitializerMemberValue) {
for(PsiAnnotationMemberValue v:((PsiArrayInitializerMemberValue)value).getInitializers()) {
final String name = StringUtil.stripQuotesAroundValue(v.getText());
checkModifier(name);
}
} else if (value != null) {
final String name = StringUtil.stripQuotesAroundValue(value.getText());
checkModifier(name);
}
}
}
private void checkModifier(final String name) {
if (!MatchOptions.INSTANCE_MODIFIER_NAME.equals(name) &&
!PsiModifier.PACKAGE_LOCAL.equals(name) &&
Arrays.binarySearch(JavaMatchingVisitor.MODIFIERS, name) < 0
) {
throw new MalformedPatternException(SSRBundle.message("invalid.modifier.type",name));
}
}
@Override
public void visitErrorElement(PsiErrorElement element) {
super.visitErrorElement(element);
//final PsiElement parent = element.getParent();
//if (parent != myCurrent || !"';' expected".equals(element.getErrorDescription())) {
// throw new MalformedPatternException(element.getErrorDescription());
//}
}
public void setCurrent(PsiElement current) {
myCurrent = current;
}
}
ValidatingVisitor visitor = new ValidatingVisitor();
final CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, options);
final int nodeCount = compiledPattern.getNodeCount();
final NodeIterator nodes = compiledPattern.getNodes();
while (nodes.hasNext()) {
final PsiElement current = nodes.current();
visitor.setCurrent(nodeCount == 1 && current instanceof PsiExpressionStatement ? current : null);
current.accept(visitor);
nodes.advance();
}
nodes.reset();
}
@Override
public void checkReplacementPattern(Project project, ReplaceOptions options) {
MatchOptions matchOptions = options.getMatchOptions();
FileType fileType = matchOptions.getFileType();
PsiElement[] statements = MatcherImplUtil.createTreeFromText(
matchOptions.getSearchPattern(),
PatternTreeContext.Block,
fileType,
project
);
final boolean searchIsExpression = statements.length == 1 && statements[0].getLastChild() instanceof PsiErrorElement;
PsiElement[] statements2 = MatcherImplUtil.createTreeFromText(
options.getReplacement(),
PatternTreeContext.Block,
fileType,
project
);
final boolean replaceIsExpression = statements2.length == 1 && statements2[0].getLastChild() instanceof PsiErrorElement;
if (searchIsExpression != replaceIsExpression) {
throw new UnsupportedPatternException(
searchIsExpression ? SSRBundle.message("replacement.template.is.not.expression.error.message") :
SSRBundle.message("search.template.is.not.expression.error.message")
);
}
}
@Override
public LanguageFileType getDefaultFileType(LanguageFileType currentDefaultFileType) {
return StdFileTypes.JAVA;
}
@Override
Configuration[] getPredefinedTemplates() {
return JavaPredefinedConfigurations.createPredefinedTemplates();
}
@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 visitVariable(PsiVariable field) {
super.visitVariable(field);
final PsiExpression initializer = field.getInitializer();
if (initializer != null) {
final String initText = initializer.getText();
if (StructuralSearchUtil.isTypedVariable(initText)) {
final ParameterInfo initInfo = builder.findParameterization(Replacer.stripTypedVariableDecoration(initText));
if (initInfo != null) {
initInfo.setVariableInitializerContext(true);
}
}
}
}
@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);
String name = parameter.getName();
String type = parameter.getType().getCanonicalText();
if (StructuralSearchUtil.isTypedVariable(name)) {
name = Replacer.stripTypedVariableDecoration(name);
if (StructuralSearchUtil.isTypedVariable(type)) {
type = Replacer.stripTypedVariableDecoration(type);
}
ParameterInfo nameInfo = builder.findParameterization(name);
ParameterInfo typeInfo = builder.findParameterization(type);
if (nameInfo != null && typeInfo != null && !(parameter.getParent() instanceof PsiCatchSection)) {
nameInfo.setArgumentContext(false);
typeInfo.setArgumentContext(false);
typeInfo.setMethodParameterContext(true);
nameInfo.setMethodParameterContext(true);
typeInfo.setElement(parameter.getTypeElement());
}
}
}
});
}
@Override
public int handleSubstitution(final ParameterInfo info,
MatchResult match,
StringBuilder result,
int offset,
HashMap<String, MatchResult> matchMap) {
if (info.getName().equals(match.getName())) {
String replacementString = match.getMatchImage();
boolean forceAddingNewLine = false;
if (info.isMethodParameterContext()) {
StringBuilder buf = new StringBuilder();
handleMethodParameter(buf, info, matchMap);
replacementString = buf.toString();
}
else if (match.getAllSons().size() > 0 && !match.isScopeMatch()) {
// compound matches
StringBuilder buf = new StringBuilder();
MatchResult r = null;
for (final MatchResult matchResult : match.getAllSons()) {
MatchResult previous = r;
r = matchResult;
final PsiElement currentElement = r.getMatch();
if (buf.length() > 0) {
final PsiElement parent = currentElement.getParent();
if (info.isStatementContext()) {
final PsiElement previousElement = previous.getMatchRef().getElement();
if (!(previousElement instanceof PsiComment) &&
( buf.charAt(buf.length() - 1) != '}' ||
previousElement instanceof PsiDeclarationStatement
)
) {
buf.append(';');
}
final PsiElement prevSibling = currentElement.getPrevSibling();
if (prevSibling instanceof PsiWhiteSpace &&
prevSibling.getPrevSibling() == previous.getMatch()
) {
// consequent statements matched so preserve whitespacing
buf.append(prevSibling.getText());
}
else {
buf.append('\n');
}
}
else if (info.isArgumentContext()) {
buf.append(',');
}
else if (parent instanceof PsiClass) {
final PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(currentElement, PsiWhiteSpace.class);
if (prevSibling instanceof PsiJavaToken && JavaTokenType.COMMA.equals(((PsiJavaToken)prevSibling).getTokenType())) {
buf.append(',');
}
else {
buf.append('\n');
}
}
else if (parent instanceof PsiReferenceList) {
buf.append(',');
}
else {
buf.append(' ');
}
}
buf.append(r.getMatchImage());
removeExtraSemicolonForSingleVarInstanceInMultipleMatch(info, r, buf);
forceAddingNewLine = currentElement instanceof PsiComment;
}
replacementString = buf.toString();
} else {
StringBuilder buf = new StringBuilder();
if (info.isStatementContext()) {
forceAddingNewLine = match.getMatch() instanceof PsiComment;
}
buf.append(replacementString);
removeExtraSemicolonForSingleVarInstanceInMultipleMatch(info, match, buf);
replacementString = buf.toString();
}
offset = Replacer.insertSubstitution(result, offset, info, replacementString);
offset = removeExtraSemicolon(info, offset, result, match);
if (forceAddingNewLine && info.isStatementContext()) {
result.insert(info.getStartIndex() + offset + 1, '\n');
offset ++;
}
}
return offset;
}
@Override
public int processAdditionalOptions(ParameterInfo info, int offset, StringBuilder result, MatchResult r) {
if (info.isStatementContext()) {
return removeExtraSemicolon(info, offset, result, r);
}
return offset;
}
@Override
public boolean isIdentifier(PsiElement element) {
return element instanceof PsiIdentifier;
}
@Override
public Collection<String> getReservedWords() {
return Collections.singleton(PsiModifier.PACKAGE_LOCAL);
}
@Override
public boolean isDocCommentOwner(PsiElement match) {
return match instanceof PsiMember;
}
private static void handleMethodParameter(StringBuilder buf, ParameterInfo info, HashMap<String, MatchResult> matchMap) {
if(info.getElement() ==null) {
// no specific handling for name of method parameter since it is handled with type
return;
}
String name = ((PsiParameter)info.getElement().getParent()).getName();
name = StructuralSearchUtil.isTypedVariable(name) ? Replacer.stripTypedVariableDecoration(name):name;
final MatchResult matchResult = matchMap.get(name);
if (matchResult == null) return;
if (matchResult.isMultipleMatch()) {
for (MatchResult result : matchResult.getAllSons()) {
if (buf.length() > 0) {
buf.append(',');
}
appendParameter(buf, result);
}
} else {
appendParameter(buf, matchResult);
}
}
private static void appendParameter(final StringBuilder buf, final MatchResult _matchResult) {
for(Iterator<MatchResult> j = _matchResult.getAllSons().iterator();j.hasNext();) {
buf.append(j.next().getMatchImage()).append(' ').append(j.next().getMatchImage());
}
}
private static void removeExtraSemicolonForSingleVarInstanceInMultipleMatch(final ParameterInfo info, MatchResult r, StringBuilder buf) {
if (info.isStatementContext()) {
final PsiElement element = r.getMatchRef().getElement();
// remove extra ;
if (buf.charAt(buf.length()-1)==';' &&
r.getMatchImage().charAt(r.getMatchImage().length()-1)==';' &&
( element instanceof PsiReturnStatement ||
element instanceof PsiDeclarationStatement ||
element instanceof PsiExpressionStatement ||
element instanceof PsiAssertStatement ||
element instanceof PsiBreakStatement ||
element instanceof PsiContinueStatement ||
element instanceof PsiMember ||
element instanceof PsiIfStatement && !(((PsiIfStatement)element).getThenBranch() instanceof PsiBlockStatement) ||
element instanceof PsiLoopStatement && !(((PsiLoopStatement)element).getBody() instanceof PsiBlockStatement)
)
) {
// contains extra ;
buf.deleteCharAt(buf.length()-1);
}
}
}
private static int removeExtraSemicolon(ParameterInfo info, int offset, StringBuilder result, MatchResult match) {
if (info.isStatementContext()) {
int index = offset+ info.getStartIndex();
if (result.charAt(index)==';' &&
( match == null ||
( result.charAt(index-1)=='}' &&
!(match.getMatch() instanceof PsiDeclarationStatement) && // array init in dcl
!(match.getMatch() instanceof PsiNewExpression) // array initializer
) ||
( !match.isMultipleMatch() && // ; in comment
match.getMatch() instanceof PsiComment
) ||
( match.isMultipleMatch() && // ; in comment
match.getAllSons().get( match.getAllSons().size() - 1 ).getMatch() instanceof PsiComment
)
)
) {
result.deleteCharAt(index);
--offset;
}
}
return offset;
}
}