summaryrefslogtreecommitdiff
path: root/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java')
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java426
1 files changed, 426 insertions, 0 deletions
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java
new file mode 100644
index 000000000000..32cb2f83c36f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java
@@ -0,0 +1,426 @@
+package com.intellij.structuralsearch.plugin.replace.impl;
+
+import com.intellij.codeInsight.template.Template;
+import com.intellij.codeInsight.template.TemplateManager;
+import com.intellij.lang.Language;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.*;
+import com.intellij.psi.codeStyle.CodeStyleManager;
+import com.intellij.psi.search.LocalSearchScope;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil;
+import com.intellij.structuralsearch.impl.matcher.PatternTreeContext;
+import com.intellij.structuralsearch.impl.matcher.predicates.ScriptSupport;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: Mar 4, 2004
+ * Time: 9:19:34 PM
+ */
+public class Replacer {
+ private final Project project;
+ private ReplacementBuilder replacementBuilder;
+ private ReplaceOptions options;
+ private ReplacementContext context;
+ private StructuralReplaceHandler replaceHandler;
+
+ public Replacer(Project project, ReplaceOptions options) {
+ this.project = project;
+ this.options = options;
+ }
+
+ public static String stripTypedVariableDecoration(final String type) {
+ return type.substring(1,type.length()-1);
+ }
+
+ public static int insertSubstitution(StringBuilder result, int offset, final ParameterInfo info, String image) {
+ if (image.length() > 0) result.insert(offset+ info.getStartIndex(),image);
+ offset += image.length();
+ return offset;
+ }
+
+ public String testReplace(String in, String what, String by, ReplaceOptions options) throws IncorrectOperationException {
+ return testReplace(in, what, by, options,false);
+ }
+
+ public String testReplace(String in, String what, String by, ReplaceOptions options, boolean filePattern) {
+ FileType type = options.getMatchOptions().getFileType();
+ return testReplace(in, what, by, options, filePattern, false, type, null);
+ }
+
+ public String testReplace(String in, String what, String by, ReplaceOptions options, boolean filePattern, boolean createPhysicalFile,
+ FileType sourceFileType, Language sourceDialect) {
+ this.options = options;
+ this.options.getMatchOptions().setSearchPattern(what);
+ this.options.setReplacement(by);
+ replacementBuilder=null;
+ context = null;
+ replaceHandler = null;
+
+ this.options.getMatchOptions().clearVariableConstraints();
+ MatcherImplUtil.transform(this.options.getMatchOptions());
+
+ checkSupportedReplacementPattern(project, options);
+
+ Matcher matcher = new Matcher(project);
+ try {
+ PsiElement firstElement, lastElement, parent;
+
+ if (options.getMatchOptions().getScope() == null) {
+ PsiElement[] elements = MatcherImplUtil.createTreeFromText(
+ in,
+ filePattern ? PatternTreeContext.File : PatternTreeContext.Block,
+ sourceFileType, sourceDialect, null,
+ project,
+ createPhysicalFile
+ );
+
+ firstElement = elements[0];
+ lastElement = elements[elements.length-1];
+ parent = firstElement.getParent();
+
+ this.options.getMatchOptions().setScope(
+ new LocalSearchScope(parent)
+ );
+ } else {
+ parent = ((LocalSearchScope)options.getMatchOptions().getScope()).getScope()[0];
+ firstElement = parent.getFirstChild();
+ lastElement = parent.getLastChild();
+ }
+
+ this.options.getMatchOptions().setResultIsContextMatch(true);
+ CollectingMatchResultSink sink = new CollectingMatchResultSink();
+ matcher.testFindMatches(sink, this.options.getMatchOptions());
+
+ final List<ReplacementInfo> resultPtrList = new ArrayList<ReplacementInfo>();
+
+ for (final MatchResult result : sink.getMatches()) {
+ resultPtrList.add(buildReplacement(result));
+ }
+
+ sink.getMatches().clear();
+
+ int startOffset = firstElement.getTextRange().getStartOffset();
+ int endOffset = filePattern ?0: parent.getTextLength() - (lastElement.getTextRange().getEndOffset());
+
+ // get nodes from text may contain
+ PsiElement prevSibling = firstElement.getPrevSibling();
+ if (prevSibling instanceof PsiWhiteSpace) {
+ startOffset -= prevSibling.getTextLength() - 1;
+ }
+
+ PsiElement nextSibling = lastElement.getNextSibling();
+ if (nextSibling instanceof PsiWhiteSpace) {
+ endOffset -= nextSibling.getTextLength() - 1;
+ }
+
+ replaceAll(resultPtrList);
+
+ String result = parent.getText();
+ result = result.substring(startOffset);
+ result = result.substring(0,result.length() - endOffset);
+
+ return result;
+ }
+ catch (Exception e) {
+ throw new IncorrectOperationException(e);
+ }
+ finally {
+ options.getMatchOptions().setScope(null);
+ }
+ }
+
+ public void replaceAll(final List<ReplacementInfo> resultPtrList) {
+ PsiElement lastAffectedElement = null;
+ PsiElement currentAffectedElement;
+
+ for (ReplacementInfo info : resultPtrList) {
+ PsiElement element = info.getMatch(0);
+ initContextAndHandler(element);
+ if (replaceHandler != null) {
+ replaceHandler.prepare(info);
+ }
+ }
+
+ for (final ReplacementInfo aResultPtrList : resultPtrList) {
+ currentAffectedElement = doReplace(aResultPtrList);
+
+ if (currentAffectedElement != lastAffectedElement) {
+ if (lastAffectedElement != null) reformatAndPostProcess(lastAffectedElement);
+ lastAffectedElement = currentAffectedElement;
+ }
+ }
+
+ reformatAndPostProcess(lastAffectedElement);
+ }
+
+ public void replace(ReplacementInfo info) {
+ PsiElement element = info.getMatch(0);
+ initContextAndHandler(element);
+
+ if (replaceHandler != null) {
+ replaceHandler.prepare(info);
+ }
+ reformatAndPostProcess(doReplace(info));
+ }
+
+ @Nullable
+ private PsiElement doReplace(final ReplacementInfo info) {
+ final ReplacementInfoImpl replacementInfo = (ReplacementInfoImpl)info;
+ final PsiElement element = replacementInfo.matchesPtrList.get(0).getElement();
+
+ if (element==null || !element.isWritable() || !element.isValid()) return null;
+
+ final PsiElement elementParent = element.getParent();
+
+ //noinspection HardCodedStringLiteral
+ CommandProcessor.getInstance().executeCommand(
+ project,
+ new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(
+ new Runnable() {
+ public void run() {
+ doReplace(element, replacementInfo);
+ }
+ }
+ );
+ PsiDocumentManager.getInstance(project).commitAllDocuments();
+ }
+ },
+ "ssreplace",
+ "test"
+ );
+
+ if (!elementParent.isValid() || !elementParent.isWritable()) {
+ return null;
+ }
+
+ return elementParent;
+ }
+
+ private void reformatAndPostProcess(final PsiElement elementParent) {
+ if (elementParent == null) return;
+ final Runnable action = new Runnable() {
+ public void run() {
+ final PsiFile containingFile = elementParent.getContainingFile();
+
+ if (containingFile != null && options.isToReformatAccordingToStyle()) {
+ if (containingFile.getVirtualFile() != null) {
+ PsiDocumentManager.getInstance(project)
+ .commitDocument(FileDocumentManager.getInstance().getDocument(containingFile.getVirtualFile()));
+ }
+
+ final int parentOffset = elementParent.getTextRange().getStartOffset();
+ CodeStyleManager.getInstance(project)
+ .reformatRange(containingFile, parentOffset, parentOffset + elementParent.getTextLength(), true);
+ }
+ if (replaceHandler != null) {
+ replaceHandler.postProcess(elementParent, options);
+ }
+ }
+ };
+
+ CommandProcessor.getInstance().executeCommand(
+ project,
+ new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(action);
+ }
+ },
+ "reformat and shorten refs after ssr",
+ "test"
+ );
+ }
+
+ private void doReplace(final PsiElement elementToReplace,
+ final ReplacementInfoImpl info) {
+ CodeStyleManager.getInstance(project).performActionWithFormatterDisabled(new Runnable() {
+ public void run() {
+ initContextAndHandler(elementToReplace);
+
+ context.replacementInfo = info;
+
+ if (replaceHandler != null) {
+ replaceHandler.replace(info, options);
+ }
+ }
+ }
+ );
+ }
+
+ private void initContextAndHandler(PsiElement psiContext) {
+ if (context == null) {
+ context = new ReplacementContext(options, project);
+ }
+ if (replaceHandler == null) {
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(psiContext);
+ if (profile != null) {
+ replaceHandler = profile.getReplaceHandler(this.context);
+ }
+ }
+ }
+
+ public static void handleComments(final PsiElement el, final PsiElement replacement, ReplacementContext context) throws IncorrectOperationException {
+ ReplacementInfoImpl replacementInfo = context.replacementInfo;
+ if (replacementInfo.elementToVariableNameMap == null) {
+ replacementInfo.elementToVariableNameMap = new HashMap<PsiElement, String>(1);
+ Map<String, MatchResult> variableMap = replacementInfo.variableMap;
+ if (variableMap != null) {
+ for(String name:variableMap.keySet()) {
+ fill(name,replacementInfo.variableMap.get(name),replacementInfo.elementToVariableNameMap);
+ }
+ }
+ }
+
+ PsiElement lastChild = el.getLastChild();
+ if (lastChild instanceof PsiComment &&
+ replacementInfo.elementToVariableNameMap.get(lastChild) == null &&
+ !(replacement.getLastChild() instanceof PsiComment)
+ ) {
+ PsiElement firstElementAfterStatementEnd = lastChild;
+ for(PsiElement curElement=firstElementAfterStatementEnd.getPrevSibling();curElement!=null;curElement = curElement.getPrevSibling()) {
+ if (!(curElement instanceof PsiWhiteSpace) && !(curElement instanceof PsiComment)) break;
+ firstElementAfterStatementEnd = curElement;
+ }
+ replacement.addRangeAfter(firstElementAfterStatementEnd,lastChild,replacement.getLastChild());
+ }
+
+ final PsiElement firstChild = el.getFirstChild();
+ if (firstChild instanceof PsiComment &&
+ !(firstChild instanceof PsiDocCommentBase) &&
+ replacementInfo.elementToVariableNameMap.get(firstChild) == null
+ ) {
+ PsiElement lastElementBeforeStatementStart = firstChild;
+
+ for(PsiElement curElement=lastElementBeforeStatementStart.getNextSibling();curElement!=null;curElement = curElement.getNextSibling()) {
+ if (!(curElement instanceof PsiWhiteSpace) && !(curElement instanceof PsiComment)) break;
+ lastElementBeforeStatementStart = curElement;
+ }
+ replacement.addRangeBefore(firstChild,lastElementBeforeStatementStart,replacement.getFirstChild());
+ }
+ }
+
+ private static void fill(final String name, final MatchResult matchResult, final Map<PsiElement, String> elementToVariableNameMap) {
+ boolean b = matchResult.isMultipleMatch() || matchResult.isScopeMatch();
+ if(matchResult.hasSons() && b) {
+ for(MatchResult r:matchResult.getAllSons()) {
+ fill(name, r, elementToVariableNameMap);
+ }
+ } else if (!b && matchResult.getMatchRef() != null) {
+ elementToVariableNameMap.put(matchResult.getMatch(),name);
+ }
+ }
+
+ public static void checkSupportedReplacementPattern(Project project, ReplaceOptions options) throws UnsupportedPatternException {
+ try {
+ String search = options.getMatchOptions().getSearchPattern();
+ String replacement = options.getReplacement();
+ FileType fileType = options.getMatchOptions().getFileType();
+ Template template = TemplateManager.getInstance(project).createTemplate("","",search);
+ Template template2 = TemplateManager.getInstance(project).createTemplate("","",replacement);
+
+ int segmentCount = template2.getSegmentsCount();
+ for(int i=0;i<segmentCount;++i) {
+ final String replacementSegmentName = template2.getSegmentName(i);
+ final int segmentCount2 = template.getSegmentsCount();
+ int j;
+
+ for(j=0;j<segmentCount2;++j) {
+ final String searchSegmentName = template.getSegmentName(j);
+
+ if (replacementSegmentName.equals(searchSegmentName)) break;
+
+ // Reference to
+ if (replacementSegmentName.startsWith(searchSegmentName) &&
+ replacementSegmentName.charAt(searchSegmentName.length())=='_'
+ ) {
+ try {
+ Integer.parseInt(replacementSegmentName.substring(searchSegmentName.length()+1));
+ break;
+ } catch(NumberFormatException ex) {}
+ }
+ }
+
+ if (j==segmentCount2) {
+ ReplacementVariableDefinition definition = options.getVariableDefinition(replacementSegmentName);
+
+ if (definition == null || definition.getScriptCodeConstraint().length() <= 2 /*empty quotes*/) {
+ throw new UnsupportedPatternException(
+ SSRBundle.message("replacement.variable.is.not.defined.message", replacementSegmentName)
+ );
+ } else {
+ String message = ScriptSupport.checkValidScript(StringUtil.stripQuotesAroundValue(definition.getScriptCodeConstraint()));
+ if (message != null) {
+ throw new UnsupportedPatternException(
+ SSRBundle.message("replacement.variable.is.not.valid", replacementSegmentName, message)
+ );
+ }
+ }
+ }
+ }
+
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType);
+
+ profile.checkReplacementPattern(project, options);
+
+ } catch(IncorrectOperationException ex) {
+ throw new UnsupportedPatternException(SSRBundle.message("incorrect.pattern.message"));
+ }
+ }
+
+ public ReplacementInfo buildReplacement(MatchResult result) {
+ List<SmartPsiElementPointer> l = new ArrayList<SmartPsiElementPointer>();
+ SmartPointerManager manager = SmartPointerManager.getInstance(project);
+
+ if (MatchResult.MULTI_LINE_MATCH.equals(result.getName())) {
+ for(Iterator<MatchResult> i=result.getAllSons().iterator();i.hasNext();) {
+ final MatchResult r = i.next();
+
+ if (MatchResult.LINE_MATCH.equals(r.getName())) {
+ PsiElement element = r.getMatchRef().getElement();
+
+ if (element instanceof PsiDocCommentBase) { // doc comment is not collapsed when created in block
+ if (i.hasNext()) {
+ MatchResult matchResult = i.next();
+
+ if (MatchResult.LINE_MATCH.equals(matchResult.getName()) &&
+ StructuralSearchUtil.isDocCommentOwner(matchResult.getMatch())) {
+ element = matchResult.getMatch();
+ } else {
+ l.add( manager.createSmartPsiElementPointer(element) );
+ element = matchResult.getMatch();
+ }
+ }
+ }
+ l.add( manager.createSmartPsiElementPointer(element) );
+ }
+ }
+ } else {
+ l.add( manager.createSmartPsiElementPointer(result.getMatchRef().getElement()));
+ }
+
+ ReplacementInfoImpl replacementInfo = new ReplacementInfoImpl();
+
+ replacementInfo.matchesPtrList = l;
+ if (replacementBuilder==null) {
+ replacementBuilder = new ReplacementBuilder(project,options);
+ }
+ replacementInfo.result = replacementBuilder.process(result, replacementInfo, options.getMatchOptions().getFileType());
+ replacementInfo.matchResult = result;
+
+ return replacementInfo;
+ }
+}