blob: 909b44799a0268559948d7fa3a46ea6fe839b1c0 [file] [log] [blame]
/*
* Copyright 2000-2010 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 com.intellij.refactoring.changeSignature;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.ide.highlighter.HighlighterFactory;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Splitter;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.ui.*;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Set;
public abstract class CallerChooserBase<M extends PsiElement> extends DialogWrapper {
private final M myMethod;
private final Alarm myAlarm = new Alarm();
private MethodNodeBase<M> myRoot;
protected final Project myProject;
private Tree myTree;
private final Consumer<Set<M>> myCallback;
private TreeSelectionListener myTreeSelectionListener;
private Editor myCallerEditor;
private Editor myCalleeEditor;
private final boolean myInitDone;
private final String myFileName;
protected abstract MethodNodeBase<M> createTreeNode(M method, HashSet<M> called, Runnable cancelCallback);
protected abstract M[] findDeepestSuperMethods(M method);
public CallerChooserBase(M method, Project project, String title, Tree previousTree, String fileName, Consumer<Set<M>> callback) {
super(true);
myMethod = method;
myProject = project;
myTree = previousTree;
myFileName = fileName;
myCallback = callback;
setTitle(title);
init();
myInitDone = true;
}
public Tree getTree() {
return myTree;
}
@Override
protected JComponent createCenterPanel() {
Splitter splitter = new Splitter(false, (float)0.6);
JPanel result = new JPanel(new BorderLayout());
if (myTree == null) {
myTree = createTree();
}
else {
final CheckedTreeNode root = (CheckedTreeNode)myTree.getModel().getRoot();
myRoot = (MethodNodeBase)root.getFirstChild();
}
myTreeSelectionListener = new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
final TreePath path = e.getPath();
if (path != null) {
final MethodNodeBase<M> node = (MethodNodeBase)path.getLastPathComponent();
myAlarm.cancelAllRequests();
myAlarm.addRequest(new Runnable() {
@Override
public void run() {
updateEditorTexts(node);
}
}, 300);
}
}
};
myTree.getSelectionModel().addTreeSelectionListener(myTreeSelectionListener);
JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
splitter.setFirstComponent(scrollPane);
final JComponent callSitesViewer = createCallSitesViewer();
TreePath selectionPath = myTree.getSelectionPath();
if (selectionPath == null) {
selectionPath = new TreePath(myRoot.getPath());
myTree.getSelectionModel().addSelectionPath(selectionPath);
}
final MethodNodeBase<M> node = (MethodNodeBase)selectionPath.getLastPathComponent();
updateEditorTexts(node);
splitter.setSecondComponent(callSitesViewer);
result.add(splitter);
return result;
}
private void updateEditorTexts(final MethodNodeBase<M> node) {
final MethodNodeBase<M> parentNode = (MethodNodeBase)node.getParent();
final String callerText = node != myRoot ? getText(node.getMethod()) : "";
final Document callerDocument = myCallerEditor.getDocument();
final String calleeText = node != myRoot ? getText(parentNode.getMethod()) : "";
final Document calleeDocument = myCalleeEditor.getDocument();
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
callerDocument.setText(callerText);
calleeDocument.setText(calleeText);
}
});
final M caller = node.getMethod();
final PsiElement callee = parentNode != null ? parentNode.getElementToSearch() : null;
if (caller != null && caller.isPhysical() && callee != null) {
HighlightManager highlighter = HighlightManager.getInstance(myProject);
EditorColorsManager colorManager = EditorColorsManager.getInstance();
TextAttributes attributes = colorManager.getGlobalScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES);
int start = getStartOffset(caller);
for (PsiElement element : findElementsToHighlight(caller, callee)) {
highlighter.addRangeHighlight(myCallerEditor, element.getTextRange().getStartOffset() - start,
element.getTextRange().getEndOffset() - start, attributes, false, null);
}
}
}
protected Collection<PsiElement> findElementsToHighlight(M caller, PsiElement callee) {
Query<PsiReference> references = ReferencesSearch.search(callee, new LocalSearchScope(caller), false);
return ContainerUtil.mapNotNull(references, new Function<PsiReference, PsiElement>() {
@Override
public PsiElement fun(PsiReference psiReference) {
return psiReference.getElement();
}
});
}
@Override
public void dispose() {
if (myTree != null) {
myTree.removeTreeSelectionListener(myTreeSelectionListener);
EditorFactory.getInstance().releaseEditor(myCallerEditor);
EditorFactory.getInstance().releaseEditor(myCalleeEditor);
}
super.dispose();
}
private String getText(final M method) {
if (method == null) return "";
final PsiFile file = method.getContainingFile();
Document document = PsiDocumentManager.getInstance(myProject).getDocument(file);
if (document != null) {
final int start = document.getLineStartOffset(document.getLineNumber(method.getTextRange().getStartOffset()));
final int end = document.getLineEndOffset(document.getLineNumber(method.getTextRange().getEndOffset()));
return document.getText().substring(start, end);
}
return "";
}
private int getStartOffset(@NotNull final M method) {
final PsiFile file = method.getContainingFile();
Document document = PsiDocumentManager.getInstance(myProject).getDocument(file);
return document.getLineStartOffset(document.getLineNumber(method.getTextRange().getStartOffset()));
}
private JComponent createCallSitesViewer() {
Splitter splitter = new Splitter(true);
myCallerEditor = createEditor();
myCalleeEditor = createEditor();
final JComponent callerComponent = myCallerEditor.getComponent();
callerComponent.setBorder(IdeBorderFactory.createTitledBorder(RefactoringBundle.message("caller.chooser.caller.method"),
false));
splitter.setFirstComponent(callerComponent);
final JComponent calleeComponent = myCalleeEditor.getComponent();
calleeComponent.setBorder(IdeBorderFactory.createTitledBorder(RefactoringBundle.message("caller.chooser.callee.method"),
false));
splitter.setSecondComponent(calleeComponent);
splitter.setBorder(IdeBorderFactory.createRoundedBorder());
return splitter;
}
private Editor createEditor() {
final EditorFactory editorFactory = EditorFactory.getInstance();
final Document document = editorFactory.createDocument("");
final Editor editor = editorFactory.createViewer(document, myProject);
((EditorEx)editor).setHighlighter(HighlighterFactory.createHighlighter(myProject, myFileName));
return editor;
}
private Tree createTree() {
final Runnable cancelCallback = new Runnable() {
@Override
public void run() {
if (myInitDone) {
close(CANCEL_EXIT_CODE);
}
else {
throw new ProcessCanceledException();
}
}
};
final CheckedTreeNode root = createTreeNode(null, new HashSet<M>(), cancelCallback);
myRoot = createTreeNode(myMethod, new HashSet<M>(), cancelCallback);
root.add(myRoot);
final CheckboxTree.CheckboxTreeCellRenderer cellRenderer = new CheckboxTree.CheckboxTreeCellRenderer(true, false) {
@Override
public void customizeRenderer(JTree tree,
Object value,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
if (value instanceof MethodNodeBase) {
((MethodNodeBase)value).customizeRenderer(getTextRenderer());
}
}
};
Tree tree = new CheckboxTree(cellRenderer, root, new CheckboxTreeBase.CheckPolicy(false, true, true, false));
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.getSelectionModel().setSelectionPath(new TreePath(myRoot.getPath()));
return tree;
}
private void getSelectedMethods(Set<M> methods) {
MethodNodeBase<M> node = myRoot;
getSelectedMethodsInner(node, methods);
methods.remove(node.getMethod());
}
private void getSelectedMethodsInner(final MethodNodeBase<M> node, final Set<M> allMethods) {
if (node.isChecked()) {
M method = node.getMethod();
final M[] superMethods = findDeepestSuperMethods(method);
if (superMethods.length == 0) {
allMethods.add(method);
}
else {
allMethods.addAll(Arrays.asList(superMethods));
}
final Enumeration children = node.children();
while (children.hasMoreElements()) {
getSelectedMethodsInner((MethodNodeBase)children.nextElement(), allMethods);
}
}
}
@Override
protected void doOKAction() {
final Set<M> selectedMethods = new HashSet<M>();
getSelectedMethods(selectedMethods);
myCallback.consume(selectedMethods);
super.doOKAction();
}
@Override
public JComponent getPreferredFocusedComponent() {
return myTree;
}
@Nullable
@Override
protected String getDimensionServiceKey() {
return "caller.chooser.dialog";
}
}