/*
* Copyright 2003-2015 JetBrains s.r.o.
*
* 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 jetbrains.mps.idea.core.refactoring;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.refactoring.move.moveClassesOrPackages.CommonMoveUtil;
import com.intellij.refactoring.util.MoveRenameUsageInfo;
import com.intellij.usageView.UsageInfo;
import jetbrains.mps.ide.findusages.model.SearchResults;
import jetbrains.mps.refactoring.participant.MoveNodeRefactoringParticipant;
import jetbrains.mps.refactoring.participant.RefactoringParticipantBase;
import jetbrains.mps.idea.core.psi.impl.MPSPsiModel;
import jetbrains.mps.idea.core.psi.impl.MPSPsiNode;
import jetbrains.mps.idea.core.psi.impl.MPSPsiProvider;
import jetbrains.mps.refactoring.participant.RefactoringSession;
import org.jetbrains.mps.openapi.model.EditableSModel;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.module.SearchScope;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Created by danilla on 11/11/15.
*/
public class UpdatePsiReferencesMoveParticipant extends RefactoringParticipantBase<SNodeReference, SNodeReference, SNode, SNode> implements MoveNodeRefactoringParticipant<SNodeReference, SNodeReference> {
private MPSPsiProvider myPsiProvider;
public UpdatePsiReferencesMoveParticipant(MPSPsiProvider psiProvider) {
myPsiProvider = psiProvider;
}
private final static String RELOAD_REFACTORING_SESSION_FLAG = "refactoringSession.updatePsiReferences.reloadFlag";
private void reloadModelPsi(SModel smodel, RefactoringSession session) {
// we can do it once after all movements ad before any calls of myPsiProvider.getPsi(finalNode)
if (session.getObject(RELOAD_REFACTORING_SESSION_FLAG) == null) {
session.putObject(RELOAD_REFACTORING_SESSION_FLAG, new HashSet<SModelReference>());
}
Set<SModelReference> reloadedModels = (Set<SModelReference>) session.getObject(RELOAD_REFACTORING_SESSION_FLAG);
if (!reloadedModels.contains(smodel.getReference())) {
// TODO remove this once/if we map smodel events to psi events synchronously
// currently it's done in the end of command
// Reloading model is needed because later we use psi element corresponding to finalNode
// Saving is needed because in MPSPsiModel we rely in roots' virtual files in case of file-per-root persistence.
if (smodel instanceof EditableSModel) {
// unfortunately we don't do check if sNode is root, because afterMove() is called in random order
// not reflecting the order of actual moves
((EditableSModel) smodel).save();
MPSPsiModel psiModel = myPsiProvider.getPsi(smodel);
psiModel.reloadAll();
}
reloadedModels.add(smodel.getReference());
}
}
@Override
public MoveNodeRefactoringDataCollector<SNodeReference, SNodeReference> getDataCollector() {
return new MoveNodeRefactoringDataCollector<SNodeReference, SNodeReference>() {
@Override
public SNodeReference beforeMove(SNode sNode) {
return sNode.getReference();
}
@Override
public SNodeReference afterMove(SNode sNode) {
return sNode.getReference();
}
};
}
@Override
public List<Option> getAvailableOptions(SNodeReference movedNode, SRepository sRepository) {
return Collections.emptyList();
}
public KeepOldNodes shouldKeepOldNode() {
return KeepOldNodes.REMOVE;
}
@Override
public List<Change<SNodeReference, SNodeReference>> getChanges(final SNodeReference nodeToMove, SRepository sRepository, List<Option> list, SearchScope searchScope) {
// NOTE: this will be called as many times as many projects there are open currently
// because extension points are per application, but psiProvider is per project.
// Every MPSPsiProvider (which is per project) happens to build psi models for all models, including other
// projects
PsiElement psiNodeToMove = myPsiProvider.getPsi(nodeToMove.resolve(sRepository));
final List<PsiReference> usages = getAffectedNodes(psiNodeToMove);
List<Change<SNodeReference, SNodeReference>> changes = new ArrayList<>();
for (final PsiReference usage : usages) {
final List<UsageInfo> usageInfos = Collections.singletonList(new MoveRenameUsageInfo(usage, usage.resolve()));
changes.add(new ChangeBase<SNodeReference, SNodeReference>() {
@Override
public SearchResults<SNode> getSearchResults() {
return new SearchResults<>(Collections.singleton(nodeToMove.resolve(sRepository)), Collections.singletonList(new PsiSearchResult(usage)));
}
@Override
public KeepOldNodes needsToPreserveOldNode() {
return shouldKeepOldNode();
}
@Override
public void confirm(final SNodeReference finalNode, SRepository sRepository, RefactoringSession refactoringSession) {
reloadModelPsi(finalNode.resolve(sRepository).getModel(), refactoringSession);
PsiElement psiNodeToMove1;
if (shouldKeepOldNode() != KeepOldNodes.REMOVE) {
reloadModelPsi(nodeToMove.resolve(sRepository).getModel(), refactoringSession);
psiNodeToMove1 = myPsiProvider.getPsi(nodeToMove.resolve(sRepository));
} else {
psiNodeToMove1 = psiNodeToMove;
}
refactoringSession.registerChange(new Runnable() {
@Override
public void run() {
PsiElement targetElement = myPsiProvider.getPsi(finalNode);
assert targetElement != null : "Failed to get PSI for target node of move refactoring";
updateUsages(usageInfos, psiNodeToMove1, targetElement);
}
});
}
});
}
return changes;
}
protected void updateUsages(List<UsageInfo> usageInfos, PsiElement psiElement, PsiElement targetElement) {
Map<PsiElement, PsiElement> old2New = Collections.singletonMap(psiElement, targetElement);
CommonMoveUtil.retargetUsages(usageInfos.toArray(UsageInfo.EMPTY_ARRAY), old2New);
}
private List<PsiReference> getAffectedNodes(PsiElement psiElement) {
return ReferencesSearch.search(psiElement).findAll()
.stream()
.filter(psiReference -> !(psiReference.getElement() instanceof MPSPsiNode))
.collect(Collectors.toList());
}
}