package com.intellij.javascript.flex.refactoring.moveClass;
import com.intellij.javascript.flex.resolve.ActionScriptClassResolver;
import com.intellij.lang.javascript.JSBundle;
import com.intellij.lang.javascript.flex.FlexBundle;
import com.intellij.lang.javascript.flex.ImportUtils;
import com.intellij.lang.javascript.psi.JSFunction;
import com.intellij.lang.javascript.psi.JSReferenceExpression;
import com.intellij.lang.javascript.psi.JSVarStatement;
import com.intellij.lang.javascript.psi.JSVariable;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList;
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.impl.JSPsiImplUtils;
import com.intellij.lang.javascript.psi.resolve.JSNamedElementKind;
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil;
import com.intellij.lang.javascript.refactoring.FormatFixer;
import com.intellij.lang.javascript.refactoring.JSChangeVisibilityUtil;
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.validation.fixes.CreateClassOrInterfaceFix;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.move.MoveCallback;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.util.NonCodeUsageInfo;
import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.*;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FlexMoveInnerClassProcessor extends BaseRefactoringProcessor {
private final JSQualifiedNamedElement myElement;
private final PsiDirectory myTargetDirectory;
private final String myClassName;
private final String myPackageName;
private final boolean mySearchInComments;
private final boolean mySearchTextOccurences;
@Nullable private final MoveCallback myMoveCallback;
private NonCodeUsageInfo[] myNonCodeUsages;
public FlexMoveInnerClassProcessor(JSQualifiedNamedElement element,
PsiDirectory targetDirectory,
String className,
String packageName,
boolean searchInComments,
boolean searchTextOccurences,
@Nullable MoveCallback moveCallback) {
super(element.getProject());
myElement = element;
myTargetDirectory = targetDirectory;
myClassName = className;
myPackageName = packageName;
mySearchInComments = searchInComments;
mySearchTextOccurences = searchTextOccurences;
myMoveCallback = moveCallback;
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
return new FlexMoveInnerClassUsageViewDescriptor();
}
@NotNull
@Override
protected UsageInfo[] findUsages() {
final Collection<UsageInfo> result = Collections.synchronizedCollection(new ArrayList<UsageInfo>());
ReferencesSearch.search(myElement, new LocalSearchScope(myElement.getContainingFile())).forEach(reference -> {
final PsiElement element = reference.getElement();
if (!(element instanceof JSReferenceExpression)) {
return true;
}
if (JSResolveUtil.isSelfReference(element)) {
return true;
}
result.add(new UsageInfo(element));
return true;
});
if (myElement instanceof JSClass) {
final JSFunction constructor = ((JSClass)myElement).getConstructor();
if (constructor != null) {
result.add(new UsageInfo(constructor));
JSRefactoringUtil.addConstructorUsages((JSClass)myElement, result);
}
}
TextOccurrencesUtil.findNonCodeUsages(myElement, myElement.getName(), mySearchInComments, mySearchTextOccurences,
StringUtil.getQualifiedName(myPackageName, myClassName), result);
return UsageViewUtil.removeDuplicatedUsages(result.toArray(new UsageInfo[result.size()]));
}
@Override
protected void performRefactoring(@NotNull UsageInfo[] usages) {
try {
CreateClassOrInterfaceFix.createClass(myClassName, myPackageName, myTargetDirectory, false);
}
catch (Exception e) {
Messages.showErrorDialog(myProject, e.getMessage(), getCommandName());
return;
}
final PsiFile sourceFile = myElement.getContainingFile();
Collection<String> importsInTargetFile = new HashSet<>();
Collection<String> namespacesInTargetFile = new HashSet<>();
List<FormatFixer> formatters = new ArrayList<>();
//JSRefactoringUtil.addRemovalFormatters(mySourceClass, myMembersToMove, Condition.TRUE, Condition.TRUE, postponedFormatters);
JSClass targetClass = myElement instanceof JSClass ? (JSClass)myElement : null;
JSRefactoringUtil.fixOutgoingReferences(myElement, importsInTargetFile, namespacesInTargetFile, Collections.singletonList(
((JSAttributeListOwner)myElement)), targetClass, false, false);
myElement.setName(myClassName);
Collection<UsageInfo> usagesToProcess = new ArrayList<>(Arrays.asList(usages));
for (Iterator<UsageInfo> i = usagesToProcess.iterator(); i.hasNext();) {
UsageInfo usage = i.next();
PsiElement element;
if (usage instanceof NonCodeUsageInfo || (element = usage.getElement()) == null ||
!PsiTreeUtil.isAncestor(myElement, element, false)) {
continue;
}
if (element instanceof JSReferenceExpression) {
((JSReferenceExpression)element).bindToElement(myElement);
}
else if (element instanceof PsiNamedElement) {
((PsiNamedElement)element).setName(myClassName);
}
else {
continue;
}
i.remove();
}
final PsiElement clazz =
ActionScriptClassResolver.findClassByQNameStatic(StringUtil.getQualifiedName(myPackageName, myClassName), GlobalSearchScope.projectScope(myProject));
PsiElement toInsert = myElement instanceof JSVariable ? JSRefactoringUtil.getVarStatementCopy((JSVariable)myElement) : myElement.copy();
final PsiElement inserted = clazz.replace(toInsert);
PsiFile insertedContainingFile = inserted.getContainingFile();
JSQualifiedNamedElement newClass =
inserted instanceof JSVarStatement ? ((JSVarStatement)inserted).getVariables()[0] : (JSQualifiedNamedElement)inserted;
SmartPsiElementPointer<JSQualifiedNamedElement> newClassPointer =
SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(newClass);
JSRefactoringUtil.handleDocCommentAndFormat(inserted, formatters);
JSRefactoringUtil.deleteWithNoPostponedFormatting(myElement);
if (myPackageName.length() > 0) {
for (UsageInfo usage : usagesToProcess) {
if (usage instanceof NonCodeUsageInfo || usage.getFile() != sourceFile) continue;
final PsiElement element = usage.getElement();
if (element == null) continue;
ImportUtils.doImport(element, StringUtil.getQualifiedName(myPackageName, myClassName), true);
}
}
JSRefactoringUtil.postProcess(sourceFile, newClass, Collections.singletonList(sourceFile), importsInTargetFile, namespacesInTargetFile,
formatters, true, false);
boolean makePublic = false;
newClass = newClassPointer.getElement();
List<NonCodeUsageInfo> nonCodeUsages = new ArrayList<>();
for (UsageInfo usage : usagesToProcess) {
if (usage instanceof NonCodeUsageInfo) {
nonCodeUsages.add((NonCodeUsageInfo)usage);
}
else {
JSReferenceExpression refExpr = (JSReferenceExpression)usage.getElement();
if (refExpr == null) {
continue;
}
makePublic |= JSPsiImplUtils.getQNameForMove(refExpr, newClass) != null;
refExpr.bindToElement(newClass);
}
}
JSChangeVisibilityUtil.setVisibility((JSAttributeListOwner)newClass,
makePublic ? JSAttributeList.AccessType.PUBLIC : JSAttributeList.AccessType.PACKAGE_LOCAL);
myNonCodeUsages = nonCodeUsages.toArray(new NonCodeUsageInfo[nonCodeUsages.size()]);
if (myMoveCallback != null) {
myMoveCallback.refactoringCompleted();
}
OpenFileDescriptor descriptor =
new OpenFileDescriptor(myProject, insertedContainingFile.getVirtualFile(), newClass.getTextOffset());
FileEditorManager.getInstance(myProject).openTextEditor(descriptor, true);
}
@Override
protected String getCommandName() {
return FlexBundle.message("move.to.upper.level.command.name",
StringUtil.decapitalize(JSBundle.message(JSNamedElementKind.kind(myElement).humanReadableKey())),
myElement.getName(), StringUtil.getQualifiedName(myPackageName, myClassName));
}
@Override
protected boolean preprocessUsages(@NotNull Ref<UsageInfo[]> refUsages) {
return showConflicts(detectConflicts(), refUsages.get());
}
private MultiMap<PsiElement, String> detectConflicts() {
MultiMap<PsiElement, String> result = new MultiMap<>();
JSVisibilityUtil.Options options = new JSVisibilityUtil.Options();
options.overridePackage(myElement, myPackageName);
JSRefactoringConflictsUtil.checkOutgoingReferencesAccessibility(myElement, Collections.singletonList(myElement), null, true, result,
Conditions.alwaysTrue(), options);
return result;
}
@Override
protected boolean isPreviewUsages(@NotNull UsageInfo[] usages) {
if (UsageViewUtil.reportNonRegularUsages(usages, myProject)) {
return true;
}
else {
return super.isPreviewUsages(usages);
}
}
protected void performPsiSpoilingRefactoring() {
if (myNonCodeUsages != null) {
RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
}
}
private class FlexMoveInnerClassUsageViewDescriptor extends BaseUsageViewDescriptor {
public FlexMoveInnerClassUsageViewDescriptor() {
super(myElement);
}
@Override
public String getProcessedElementsHeader() {
return FlexBundle.message("element.to.be.moved.to.upper.level",
StringUtil.decapitalize(JSBundle.message(JSNamedElementKind.kind(myElement).humanReadableKey())),
StringUtil.getQualifiedName(myPackageName, myClassName));
}
@Override
public String getCodeReferencesText(int usagesCount, int filesCount) {
return FlexBundle.message("references.in.code.to.inner.0", UsageViewUtil.getLongName(getElements()[0])) +
UsageViewBundle.getReferencesString(usagesCount, filesCount);
}
@Override
public String getCommentReferencesText(int usagesCount, int filesCount) {
return RefactoringBundle.message("comments.elements.header", UsageViewBundle.getOccurencesString(usagesCount, filesCount));
}
}
}