/*
* Copyright 2011-present Greg Shrago
*
* 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 org.intellij.grammar.refactor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TObjectIntHashMap;
import org.intellij.grammar.generator.ParserGeneratorUtil;
import org.intellij.grammar.psi.*;
import org.intellij.grammar.psi.impl.BnfElementFactory;
import org.intellij.grammar.psi.impl.GrammarUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* Date: 8/11/11
* Time: 4:19 PM
*
* @author Vadim Romansky
*/
public class BnfInlineRuleProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance("org.intellij.grammar.refactor.BnfInlineRuleProcessor");
private BnfRule myRule;
private final PsiReference myReference;
private final boolean myInlineThisOnly;
public BnfInlineRuleProcessor(BnfRule rule, Project project, PsiReference ref, boolean isInlineThisOnly) {
super(project);
myRule = rule;
myReference = ref;
myInlineThisOnly = isInlineThisOnly;
}
@NotNull
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new BnfInlineViewDescriptor(myRule);
}
protected String getCommandName() {
return "Inline rule '"+myRule.getName()+"'";
}
@NotNull
protected UsageInfo[] findUsages() {
if (myInlineThisOnly) return new UsageInfo[]{new UsageInfo(myReference.getElement())};
List<UsageInfo> result = ContainerUtil.newArrayList();
for (PsiReference reference : ReferencesSearch.search(myRule, myRule.getUseScope(), false)) {
PsiElement element = reference.getElement();
if (GrammarUtil.isInAttributesReference(element)) continue;
result.add(new UsageInfo(element));
}
return result.toArray(new UsageInfo[result.size()]);
}
protected void refreshElements(PsiElement[] elements) {
LOG.assertTrue(elements.length == 1 && elements[0] instanceof BnfRule);
myRule = (BnfRule)elements[0];
}
protected void performRefactoring(UsageInfo[] usages) {
BnfExpression expression = myRule.getExpression();
boolean meta = ParserGeneratorUtil.Rule.isMeta(myRule);
CommonRefactoringUtil.sortDepthFirstRightLeftOrder(usages);
for (UsageInfo info : usages) {
try {
final BnfExpression element = (BnfExpression)info.getElement();
boolean metaRuleRef = GrammarUtil.isExternalReference(element);
if (meta && metaRuleRef) {
inlineMetaRuleUsage(element, expression);
}
else if (!meta && !metaRuleRef) {
inlineExpressionUsage(element, expression);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
if (!myInlineThisOnly) {
try {
myRule.delete();
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
private static void inlineExpressionUsage(BnfExpression place, BnfExpression ruleExpr) throws IncorrectOperationException {
BnfExpression replacement = BnfElementFactory.createExpressionFromText(ruleExpr.getProject(), '(' + ruleExpr.getText() + ')');
BnfExpressionOptimizer.optimize(place.replace(replacement));
}
private static void inlineMetaRuleUsage(BnfExpression place, BnfExpression expression) {
BnfRule rule = PsiTreeUtil.getParentOfType(place, BnfRule.class);
PsiElement parent = place.getParent();
final List<BnfExpression> expressionList;
if (parent instanceof BnfExternalExpression) {
expressionList = ((BnfExternalExpression)parent).getExpressionList();
}
else if (parent instanceof BnfSequence) {
expressionList = ((BnfSequence)parent).getExpressionList();
}
else if (parent instanceof BnfRule) {
expressionList = Collections.emptyList();
}
else {
LOG.error(parent);
return;
}
final TObjectIntHashMap<String> visited = new TObjectIntHashMap<>();
final LinkedList<Pair<PsiElement, PsiElement>> work = new LinkedList<>();
(expression = (BnfExpression)expression.copy()).acceptChildren(new PsiRecursiveElementWalkingVisitor() {
@Override
public void visitElement(PsiElement element) {
if (element instanceof BnfExternalExpression) {
List<BnfExpression> list = ((BnfExternalExpression)element).getExpressionList();
if (list.size() == 1) {
String text = list.get(0).getText();
int idx = visited.get(text);
if (idx == 0) visited.put(text, idx = visited.size() + 1);
if (idx < expressionList.size()) {
work.addFirst(Pair.create(element, (PsiElement)expressionList.get(idx)));
}
}
}
else {
super.visitElement(element);
}
}
});
for (Pair<PsiElement, PsiElement> pair : work) {
BnfExpressionOptimizer.optimize(pair.first.replace(pair.second));
}
inlineExpressionUsage((BnfExpression)parent, expression);
if (!(parent instanceof BnfExternalExpression)) {
for (BnfModifier modifier : rule.getModifierList()) {
if (modifier.getText().equals("external")) {
modifier.getNextSibling().delete(); // whitespace
modifier.delete();
break;
}
}
}
}
}