package com.intellij.javascript.flex.refactoring.moveClass;
import com.intellij.lang.javascript.JSBundle;
import com.intellij.lang.javascript.flex.FlexBundle;
import com.intellij.lang.javascript.psi.JSFunction;
import com.intellij.lang.javascript.psi.JSNamedElement;
import com.intellij.lang.javascript.psi.JSReferenceExpression;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeListOwner;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.lang.javascript.psi.ecmal4.JSQualifiedNamedElement;
import com.intellij.lang.javascript.psi.resolve.JSNamedElementKind;
import com.intellij.lang.javascript.refactoring.JSVisibilityUtil;
import com.intellij.lang.javascript.refactoring.util.JSRefactoringConflictsUtil;
import com.intellij.lang.javascript.refactoring.util.JSRefactoringUtil;
import com.intellij.lang.javascript.ui.JSFormatUtil;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.move.MoveCallback;
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesProcessor;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.*;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FlexMoveClassProcessor extends MoveFilesOrDirectoriesProcessor {
private final Collection<JSQualifiedNamedElement> myElements;
private final String myTargetPackage;
public FlexMoveClassProcessor(Collection<JSQualifiedNamedElement> elements,
PsiDirectory targetDirectory,
String targetPackage,
boolean searchInComments,
boolean searchForTextOccurencies,
@Nullable MoveCallback callback) {
super(elements.iterator().next().getProject(),
ContainerUtil.map2Array(elements, PsiElement.class, (Function<JSQualifiedNamedElement, PsiElement>)e -> e.getContainingFile()),
targetDirectory, true, searchInComments, searchForTextOccurencies, callback, null);
myElements = elements;
myTargetPackage = targetPackage;
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
return new FlexMoveClassUsageViewDescriptor();
}
@Override
protected String getCommandName() {
StringBuilder s = new StringBuilder();
for (JSQualifiedNamedElement element : myElements) {
if (s.length() > 0) {
s.append(", ");
}
s.append(FlexBundle.message("moved.element.description",
StringUtil.decapitalize(JSBundle.message(JSNamedElementKind.kind(element).humanReadableKey())),
element.getQualifiedName()));
}
return FlexBundle.message("move.command.name", s, JSFormatUtil.formatPackage(myTargetPackage));
}
@NotNull
@Override
protected UsageInfo[] findUsages() {
Collection<UsageInfo> result = Collections.synchronizedCollection(new ArrayList<UsageInfo>());
result.addAll(Arrays.asList(super.findUsages()));
for (JSQualifiedNamedElement element : myElements) {
if (element instanceof JSClass) {
JSRefactoringUtil.addConstructorUsages((JSClass)element, result);
}
TextOccurrencesUtil.findNonCodeUsages(element, element.getQualifiedName(), mySearchInComments, mySearchInNonJavaFiles,
StringUtil.getQualifiedName(myTargetPackage, element.getName()), result);
}
return result.toArray(new UsageInfo[result.size()]);
}
@Override
protected boolean preprocessUsages(@NotNull Ref<UsageInfo[]> refUsages) {
return showConflicts(detectConflicts(refUsages.get()), refUsages.get());
}
@Override
protected boolean isPreviewUsages(@NotNull UsageInfo[] usages) {
if (UsageViewUtil.reportNonRegularUsages(usages, myProject)) {
return true;
}
else {
return super.isPreviewUsages(usages);
}
}
private MultiMap<PsiElement, String> detectConflicts(UsageInfo[] usages) {
MultiMap<PsiElement, String> conflicts = new MultiMap<>();
final Collection<PsiElement> filesToMove = Arrays.asList(myElementsToMove);
JSVisibilityUtil.Options options = new JSVisibilityUtil.Options();
for (PsiElement file : filesToMove) {
options.overridePackage(file, myTargetPackage);
}
for (UsageInfo usage : usages) {
final PsiElement element = usage.getElement();
if (!(element instanceof JSReferenceExpression)) {
continue;
}
if (CommonRefactoringUtil.isAncestor(element, filesToMove)) {
continue;
}
JSReferenceExpression refExpr = (JSReferenceExpression)element;
final PsiElement resolved = refExpr.resolve();
if (!(resolved instanceof JSQualifiedNamedElement)) {
continue;
}
PsiElement containingClass = null;
if (resolved instanceof JSFunction &&
((JSFunction)resolved).isConstructor() &&
myElements.contains(containingClass = resolved.getParent()) || myElements.contains(resolved)) {
JSRefactoringConflictsUtil
.checkAccessibility((JSAttributeListOwner)resolved, (JSClass)containingClass, null, refExpr, conflicts, true, options);
}
}
for (PsiElement fileToMove : filesToMove) {
JSRefactoringConflictsUtil.checkOutgoingReferencesAccessibility(fileToMove, filesToMove, null, true, conflicts,
Conditions.alwaysTrue(), options);
}
// TODO module conflicts
//JSRefactoringConflictsUtil.analyzeModuleConflicts(myProject, myElements, usages, myTargetDirectory, conflicts);
return conflicts;
}
@Override
protected void retargetUsages(UsageInfo[] usages, Map<PsiElement, PsiElement> oldToNewMap) {
super.retargetUsages(usages, oldToNewMap);
for (UsageInfo usage : usages) {
if (usage instanceof JSRefactoringUtil.ConstructorUsageInfo) {
final JSRefactoringUtil.ConstructorUsageInfo constuctorUsage = (JSRefactoringUtil.ConstructorUsageInfo)usage;
final JSReferenceExpression ref = constuctorUsage.getElement();
if (ref != null && constuctorUsage.getSubject().isValid()) {
ref.bindToElement(constuctorUsage.getSubject().getContainingFile());
}
}
}
}
private class FlexMoveClassUsageViewDescriptor extends BaseUsageViewDescriptor {
public FlexMoveClassUsageViewDescriptor() {
super(PsiUtilCore.toPsiElementArray(myElements));
}
@Override
public String getProcessedElementsHeader() {
if (getElements().length == 1) {
return FlexBundle.message("element.to.be.moved.to",
JSBundle.message(JSNamedElementKind.kind((JSNamedElement)getElements()[0]).humanReadableKey()),
JSFormatUtil.formatPackage(myTargetPackage));
}
else {
return FlexBundle.message("elements.to.be.moved.to", JSFormatUtil.formatPackage(myTargetPackage));
}
}
@Override
public String getCodeReferencesText(int usagesCount, int filesCount) {
String prefix;
if (getElements().length == 1) {
prefix = FlexBundle.message("references.in.code.to.0", UsageViewUtil.getLongName(getElements()[0]));
}
else {
prefix = RefactoringBundle.message("references.found.in.code");
}
return prefix + UsageViewBundle.getReferencesString(usagesCount, filesCount);
}
@Override
public String getCommentReferencesText(int usagesCount, int filesCount) {
return RefactoringBundle.message("comments.elements.header", UsageViewBundle.getOccurencesString(usagesCount, filesCount));
}
}
}