/*
* Copyright 2003-2016 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.ide.findusages.view;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.icons.AllIcons.Toolwindows;
import com.intellij.ide.OccurenceNavigator;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task.Modal;
import jetbrains.mps.generator.GenerationFacade;
import jetbrains.mps.ide.findusages.CantLoadSomethingException;
import jetbrains.mps.ide.findusages.CantSaveSomethingException;
import jetbrains.mps.ide.findusages.IExternalizeable;
import jetbrains.mps.ide.findusages.model.IResultProvider;
import jetbrains.mps.ide.findusages.model.SearchQuery;
import jetbrains.mps.ide.findusages.model.SearchResults;
import jetbrains.mps.ide.findusages.model.SearchTask;
import jetbrains.mps.ide.findusages.model.holders.IHolder;
import jetbrains.mps.ide.findusages.model.holders.VoidHolder;
import jetbrains.mps.ide.findusages.view.treeholder.tree.DataTreeChangesNotifier;
import jetbrains.mps.ide.findusages.view.treeholder.treeview.INodeRepresentator;
import jetbrains.mps.ide.findusages.view.treeholder.treeview.UsagesTreeComponent;
import jetbrains.mps.ide.findusages.view.treeholder.treeview.ViewOptions;
import jetbrains.mps.ide.make.DefaultMakeMessageHandler;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.make.IMakeService;
import jetbrains.mps.make.MakeSession;
import jetbrains.mps.make.resources.IResource;
import jetbrains.mps.progress.ProgressMonitorAdapter;
import jetbrains.mps.project.Project;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.smodel.RepoListenerRegistrar;
import jetbrains.mps.smodel.resources.ModelsToResources;
import jetbrains.mps.util.Computable;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.module.ModelAccess;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
public class UsagesView implements IExternalizeable {
//read/write constants
private static final String QUERY = "query";
private static final String RESULT_PROVIDER = "result_provider";
private static final String CLASS_NAME = "class_name";
private static final String TREE_WRAPPER = "tree_wrapper";
private Project myProject;
//my components
private JPanel myPanel;
private UsagesTreeComponent myTreeComponent;
private String myCaption = "Usages";
private Icon myIcon = Toolwindows.ToolWindowFind;
private DataTreeChangesNotifier myChangeTracker;
private boolean myOwnChangeTracker = false; // true indicates this class shall manage repository listener (myChangeTracker)
// note: this field is not restored from XML
@Nullable
private SearchResults myLastResults;
public UsagesView(com.intellij.openapi.project.Project project, ViewOptions defaultOptions) {
this(ProjectHelper.toMPSProject(project), defaultOptions);
}
public UsagesView(Project mpsProject, ViewOptions defaultOptions) {
this(mpsProject, defaultOptions, new DataTreeChangesNotifier());
myOwnChangeTracker = true;
new RepoListenerRegistrar(mpsProject.getRepository(), myChangeTracker).attach();
}
public UsagesView(Project mpsProject, ViewOptions defaultOptions, DataTreeChangesNotifier changeTracker) {
myProject = mpsProject;
myTreeComponent = new UsagesTreeComponent(defaultOptions, myProject, changeTracker);
myPanel = new RootPanel(myTreeComponent.getOccurenceNavigator());
JPanel treeWrapperPanel = new JPanel(new BorderLayout());
JPanel treeToolbarPanel = new JPanel(new BorderLayout());
treeToolbarPanel.add(myTreeComponent.getViewToolbar(), BorderLayout.NORTH);
treeWrapperPanel.add(treeToolbarPanel, BorderLayout.WEST);
treeWrapperPanel.add(myTreeComponent, BorderLayout.CENTER);
myPanel.add(treeWrapperPanel, BorderLayout.CENTER);
myPanel.setMinimumSize(new Dimension());
myChangeTracker = changeTracker;
}
public void dispose() {
myTreeComponent.dispose();
if (myOwnChangeTracker) {
new RepoListenerRegistrar(myProject.getRepository(), myChangeTracker).detach();
}
myChangeTracker = null;
}
//----RUN STUFF----
public void setContents(SearchResults results) {
myLastResults = results;
myTreeComponent.setContents(results);
}
public void setCustomNodeRepresentator(INodeRepresentator nodeRepresentator) {
myTreeComponent.setCustomRepresentator(nodeRepresentator);
}
//----COMPONENT STUFF----
public JComponent getComponent() {
return myPanel;
}
public String getCaption() {
return myCaption;
}
public void setCaption(String caption) {
myCaption = caption;
}
public Icon getIcon() {
return myIcon;
}
public void setIcon(Icon icon) {
myIcon = icon;
}
public void setActions(AnAction... actions) {
DefaultActionGroup ag = new DefaultActionGroup();
ag.addAll(actions);
ag.addAll(myTreeComponent.getActionsToolbar());
myPanel.add(createActionsToolbar(ag, myTreeComponent), BorderLayout.WEST);
}
public void setActions(Collection<? extends AnAction> actions) {
setActions(actions.toArray(new AnAction[actions.size()]));
}
public void close() {
}
//----RESULTS MANIPUALTION STUFF----
public Set<SModel> getIncludedModels() {
return myTreeComponent.getIncludedModels();
}
public Set<SModel> getAllModels() {
return myTreeComponent.getAllModels();
}
public List<SNodeReference> getIncludedResultNodes() {
return myTreeComponent.getIncludedResultNodes();
}
public List<SNodeReference> getAllResultNodes() {
return myTreeComponent.getAllResultNodes();
}
@Nullable
public SearchResults getSearchResults() {
return myLastResults;
}
public UsagesTreeComponent getTreeComponent() {
return myTreeComponent;
}
//----SAVE/LOAD STUFF----
@Override
public void read(Element element, Project project) throws CantLoadSomethingException {
Element treeWrapperXML = element.getChild(TREE_WRAPPER);
myTreeComponent.read(treeWrapperXML, project);
}
@Override
public void write(Element element, Project project) throws CantSaveSomethingException {
Element treeWrapperXML = new Element(TREE_WRAPPER);
myTreeComponent.write(treeWrapperXML, project);
element.addContent(treeWrapperXML);
}
private static class RootPanel extends JPanel implements OccurenceNavigator, DataProvider {
private final OccurenceNavigator myOccurrenceNavigator;
public RootPanel(@Nullable OccurenceNavigator occurrenceNavigator) {
super(new BorderLayout());
myOccurrenceNavigator = occurrenceNavigator;
}
@Override
public boolean hasNextOccurence() {
return myOccurrenceNavigator != null && myOccurrenceNavigator.hasNextOccurence();
}
@Override
public boolean hasPreviousOccurence() {
return myOccurrenceNavigator != null && myOccurrenceNavigator.hasPreviousOccurence();
}
@Override
public OccurenceInfo goNextOccurence() {
return myOccurrenceNavigator != null ? myOccurrenceNavigator.goNextOccurence() : null;
}
@Override
public OccurenceInfo goPreviousOccurence() {
return myOccurrenceNavigator != null ? myOccurrenceNavigator.goPreviousOccurence() : null;
}
@Override
public String getNextOccurenceActionName() {
return myOccurrenceNavigator != null ? myOccurrenceNavigator.getNextOccurenceActionName() : "";
}
@Override
public String getPreviousOccurenceActionName() {
return myOccurrenceNavigator != null ? myOccurrenceNavigator.getPreviousOccurenceActionName() : "";
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (PlatformDataKeys.HELP_ID.is(dataId)) {
return "ideaInterface.usagesView";
}
return null;
}
}
private JPanel createActionsToolbar(ActionGroup ag, JComponent targetComponent) {
JPanel rv = new JPanel();
rv.setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 1));
ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, ag, false);
actionToolbar.setTargetComponent(targetComponent);
actionToolbar.setOrientation(SwingConstants.VERTICAL);
rv.add(actionToolbar.getComponent());
return rv;
}
protected final Project getProject() {
return myProject;
}
public static class RerunAction extends AnAction {
private final UsagesView myView;
private SearchTask mySearchTask;
private String myProgressText = "Searching";
public RerunAction(UsagesView view, String text) {
this(view, text, "", Actions.Rerun);
}
public RerunAction(UsagesView view, String text, String description, Icon icon) {
super(text, description, icon);
myView = view;
}
public void setProgressText(@NotNull String text) {
myProgressText = text;
}
public void setRunOptions(IResultProvider resultProvider, SearchQuery searchQuery) {
setRunOptions(new SearchTaskImpl(resultProvider, searchQuery));
}
public void setRunOptions(SearchTask searchTask) {
mySearchTask = searchTask;
}
@Override
public void update(@NotNull AnActionEvent e) {
super.update(e);
e.getPresentation().setEnabled(mySearchTask != null);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
assert mySearchTask != null;
if (!mySearchTask.canExecute()) {
return;
}
ProgressManager.getInstance().run(new Modal(ProjectHelper.toIdeaProject(myView.myProject), myProgressText, true) {
private SearchResults mySearchResults;
@Override
public void run(@NotNull final ProgressIndicator indicator) {
mySearchResults = mySearchTask.execute(myView.myProject.getModelAccess(), new ProgressMonitorAdapter(indicator));
}
@Override
public void onSuccess() {
if (mySearchResults != null) {
mySearchResults.removeDuplicates();
myView.setContents(mySearchResults);
}
}
});
}
}
public static final class SearchTaskImpl implements SearchTask, Runnable {
private final IResultProvider myResultProvider;
private final SearchQuery mySearchQuery;
private SearchResults myLastResults;
private ProgressMonitor myProgress;
public SearchTaskImpl(@NotNull IResultProvider resultProvider, @NotNull SearchQuery searchQuery) {
myResultProvider = resultProvider;
mySearchQuery = searchQuery;
}
public boolean canExecute() {
if (mySearchQuery.getScope() == null) {
return false;
}
final IHolder holder = mySearchQuery.getObjectHolder();
return !(holder instanceof VoidHolder) && holder.getObject() != null;
}
public Object getSearchObject() {
final IHolder objectHolder = mySearchQuery.getObjectHolder();
if (objectHolder instanceof VoidHolder) {
return null;
}
return objectHolder.getObject();
}
public SearchResults execute(ModelAccess modelAccess, ProgressMonitor progressMonitor) {
myProgress = progressMonitor;
modelAccess.runReadAction(this);
return getSearchResults();
}
@Override
public void run() {
myLastResults = myResultProvider.getResults(mySearchQuery, myProgress);
}
public SearchResults getSearchResults() {
return myLastResults;
}
@Nullable
public static SearchTaskImpl read(Element element, Project mpsProject) throws CantLoadSomethingException {
Element resultProviderXML = element.getChild(RESULT_PROVIDER);
if (resultProviderXML != null) {
String className = resultProviderXML.getAttributeValue(CLASS_NAME);
try {
IResultProvider resultProvider = (IResultProvider) Class.forName(className).newInstance();
resultProvider.read(resultProviderXML, mpsProject);
Element queryXML = element.getChild(QUERY);
SearchQuery searchQuery = new SearchQuery(queryXML, mpsProject);
return new SearchTaskImpl(resultProvider, searchQuery);
} catch (Throwable t) {
throw new CantLoadSomethingException("Can't instantiate result provider: " + className, t);
}
}
return null;
}
public void write(Element element, Project mpsProject) throws CantSaveSomethingException {
Element resultProviderXML = new Element(RESULT_PROVIDER);
resultProviderXML.setAttribute(CLASS_NAME, myResultProvider.getClass().getName());
myResultProvider.write(resultProviderXML, mpsProject);
element.addContent(resultProviderXML);
Element queryXML = new Element(QUERY);
mySearchQuery.write(queryXML, mpsProject);
element.addContent(queryXML);
}
}
public static class RebuildAction extends AnAction {
private final AtomicReference<MakeSession> myMakeSession = new AtomicReference<MakeSession>();
private final UsagesView myView;
public RebuildAction(UsagesView view) {
this(view, "Rebuild models", "", Actions.Compile);
}
public RebuildAction(UsagesView view, String text, String description, Icon icon) {
super(text, description, icon);
myView = view;
}
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabled(myMakeSession.get() == null && !IMakeService.INSTANCE.isSessionActive());
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
final Project mpsProject = myView.myProject;
Iterable<IResource> makeRes = new ModelAccessHelper(mpsProject.getModelAccess()).runReadAction(new Computable<Iterable<IResource>>() {
@Override
public Iterable<IResource> compute() {
List<SModel> models = new ArrayList<SModel>();
for (SModel modelDescriptor : myView.getIncludedModels()) {
if (GenerationFacade.canGenerate(modelDescriptor)) {
models.add(modelDescriptor);
}
}
return new ModelsToResources(models).resources(false);
}
});
if (myMakeSession.compareAndSet(null, new MakeSession(mpsProject, new DefaultMakeMessageHandler(mpsProject), false))) {
try {
if (IMakeService.INSTANCE.get().openNewSession(myMakeSession.get())) {
IMakeService.INSTANCE.get().make(myMakeSession.get(), makeRes);
}
} finally {
myMakeSession.set(null);
}
}
}
}
}