/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.actions;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.che.api.promises.client.Function;
import org.eclipse.che.api.promises.client.FunctionException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.api.promises.client.js.Promises;
import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.action.ActionGroup;
import org.eclipse.che.ide.api.action.ActionManager;
import org.eclipse.che.ide.api.action.DefaultActionGroup;
import org.eclipse.che.ide.api.action.IdeActions;
import org.eclipse.che.ide.api.action.Presentation;
import org.eclipse.che.ide.api.action.PromisableAction;
import org.eclipse.che.ide.api.parts.PerspectiveManager;
import org.eclipse.che.ide.ui.toolbar.PresentationFactory;
import org.eclipse.che.ide.util.Pair;
import org.eclipse.che.ide.util.loging.Log;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* @author Evgen Vidolob
* @author Vlad Zhukovskyi
*/
public class ActionManagerImpl implements ActionManager {
public static final String[] EMPTY_ARRAY = new String[0];
private final Map<String, Object> myId2Action;
private final Map<String, Set<String>> myPlugin2Id;
private final Map<String, Integer> myId2Index;
private final Map<Object, String> myAction2Id;
private final PresentationFactory presentationFactory;
private final Provider<PerspectiveManager> managerProvider;
private int myRegisteredActionsCount;
@Inject
public ActionManagerImpl(PresentationFactory presentationFactory, Provider<PerspectiveManager> managerProvider) {
this.presentationFactory = presentationFactory;
this.managerProvider = managerProvider;
this.myId2Action = new HashMap<>();
this.myPlugin2Id = new HashMap<>();
this.myId2Index = new HashMap<>();
this.myAction2Id = new HashMap<>();
registerDefaultActionGroups();
}
private void registerDefaultActionGroups() {
// register default action groups for main menu
DefaultActionGroup mainMenu = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_MAIN_MENU, mainMenu);
DefaultActionGroup workspaceGroup = new DefaultActionGroup("Workspace", true, this);
registerAction(IdeActions.GROUP_WORKSPACE, workspaceGroup);
mainMenu.add(workspaceGroup);
DefaultActionGroup projectGroup = new DefaultActionGroup("Project", true, this);
registerAction(IdeActions.GROUP_PROJECT, projectGroup);
mainMenu.add(projectGroup);
DefaultActionGroup editGroup = new DefaultActionGroup("Edit", true, this);
registerAction(IdeActions.GROUP_EDIT, editGroup);
mainMenu.add(editGroup);
DefaultActionGroup assistantGroup = new DefaultActionGroup("Assistant", true, this);
registerAction(IdeActions.GROUP_ASSISTANT, assistantGroup);
mainMenu.add(assistantGroup);
DefaultActionGroup runGroup = new DefaultActionGroup("Run", true, this);
registerAction(IdeActions.GROUP_RUN, runGroup);
mainMenu.add(runGroup);
DefaultActionGroup profileGroup = new DefaultActionGroup("Profile", true, this);
registerAction(IdeActions.GROUP_PROFILE, profileGroup);
mainMenu.add(profileGroup);
DefaultActionGroup helpGroup = new DefaultActionGroup("Help", true, this);
registerAction(IdeActions.GROUP_HELP, helpGroup);
mainMenu.add(helpGroup);
// register default action groups for context menu
DefaultActionGroup mainContextMenuGroup = new DefaultActionGroup(IdeActions.GROUP_MAIN_CONTEXT_MENU, false, this);
registerAction(IdeActions.GROUP_MAIN_CONTEXT_MENU, mainContextMenuGroup);
DefaultActionGroup runContextMenuGroup = new DefaultActionGroup(IdeActions.GROUP_RUN_CONTEXT_MENU, false, this);
registerAction(IdeActions.GROUP_RUN_CONTEXT_MENU, runContextMenuGroup);
mainContextMenuGroup.add(runContextMenuGroup);
DefaultActionGroup debugContextMenuGroup = new DefaultActionGroup(IdeActions.GROUP_DEBUG_CONTEXT_MENU, false, this);
registerAction(IdeActions.GROUP_DEBUG_CONTEXT_MENU, debugContextMenuGroup);
mainContextMenuGroup.add(debugContextMenuGroup);
// register default action groups for part menu
DefaultActionGroup partMenuGroup = new DefaultActionGroup(IdeActions.GROUP_PART_MENU, false, this);
registerAction(IdeActions.GROUP_PART_MENU, partMenuGroup);
DefaultActionGroup leftMainMenu = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_LEFT_MAIN_MENU, leftMainMenu);
DefaultActionGroup rightMainMenu = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_RIGHT_MAIN_MENU, rightMainMenu);
DefaultActionGroup projectExplorerContextMenuGroup =
new DefaultActionGroup(IdeActions.GROUP_PROJECT_EXPLORER_CONTEXT_MENU, false, this);
registerAction(IdeActions.GROUP_PROJECT_EXPLORER_CONTEXT_MENU, projectExplorerContextMenuGroup);
// Register context menu group for editor tab
registerAction(IdeActions.GROUP_EDITOR_TAB_CONTEXT_MENU,
new DefaultActionGroup(IdeActions.GROUP_EDITOR_TAB_CONTEXT_MENU, false, this));
// Register context menu group for consoles tree
registerAction(IdeActions.GROUP_CONSOLES_TREE_CONTEXT_MENU,
new DefaultActionGroup(IdeActions.GROUP_CONSOLES_TREE_CONTEXT_MENU, false, this));
// register default action groups for main toolbar
DefaultActionGroup mainToolbarGroup = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_MAIN_TOOLBAR, mainToolbarGroup);
// register default action groups for center part of toolbar
DefaultActionGroup centerToolbarGroup = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_CENTER_TOOLBAR, centerToolbarGroup);
// register default action groups for right part of toolbar
DefaultActionGroup rightToolbarGroup = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_RIGHT_TOOLBAR, rightToolbarGroup);
// register default action groups for status panel
DefaultActionGroup leftStatusPanelGroup = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_LEFT_STATUS_PANEL, leftStatusPanelGroup);
DefaultActionGroup centerStatusPanelGroup = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_CENTER_STATUS_PANEL, centerStatusPanelGroup);
DefaultActionGroup rightStatusPanelGroup = new DefaultActionGroup(this);
registerAction(IdeActions.GROUP_RIGHT_STATUS_PANEL, rightStatusPanelGroup);
}
private static void reportActionError(final String pluginId, final String message) {
if (pluginId == null) {
Log.error(ActionManagerImpl.class, message);
} else {
Log.error(ActionManagerImpl.class, pluginId, message);
}
}
@Override
public Action getAction(String id) {
return getActionImpl(id, false);
}
private Action getActionImpl(String id, boolean canReturnStub) {
return (Action)myId2Action.get(id);
}
@Override
public String getId(Action action) {
return myAction2Id.get(action);
}
@Override
public String[] getActionIds(String idPrefix) {
ArrayList<String> idList = new ArrayList<>();
for (String id : myId2Action.keySet()) {
if (id.startsWith(idPrefix)) {
idList.add(id);
}
}
return idList.toArray(new String[idList.size()]);
}
@Override
public boolean isGroup(String actionId) {
return getActionImpl(actionId, true) instanceof ActionGroup;
}
@Override
public Promise<Void> performActions(List<Pair<Action, ActionEvent>> actions, boolean breakOnFail) {
Promise<Void> promise = Promises.resolve(null);
return chainActionsRecursively(promise, actions.listIterator(), breakOnFail);
}
@Override
public void performAction(String actionId, Map<String, String> parameters) {
final Action action;
if (actionId != null && (action = getAction(actionId)) != null) {
final Presentation presentation = presentationFactory.getPresentation(action);
final ActionEvent actionEvent = new ActionEvent(presentation, this, managerProvider.get(), parameters);
action.update(actionEvent);
if (presentation.isEnabled() && presentation.isVisible()) {
action.actionPerformed(actionEvent);
}
}
}
/**
* Recursively chains the given promise with the promise that performs the next action from the given iterator.
*/
private Promise<Void> chainActionsRecursively(Promise<Void> promise,
ListIterator<Pair<Action, ActionEvent>> iterator,
boolean breakOnFail) {
if (!iterator.hasNext()) {
return promise;
}
final Pair<Action, ActionEvent> actionWithEvent = iterator.next();
final Promise<Void> derivedPromise = promise.thenPromise(new Function<Void, Promise<Void>>() {
@Override
public Promise<Void> apply(Void arg) throws FunctionException {
return promiseAction(actionWithEvent.first, actionWithEvent.second);
}
});
if (breakOnFail) {
return chainActionsRecursively(derivedPromise, iterator, breakOnFail);
}
final Promise<Void> derivedErrorSafePromise = derivedPromise.catchErrorPromise(new Function<PromiseError, Promise<Void>>() {
@Override
public Promise<Void> apply(PromiseError arg) throws FunctionException {
// 'hide' the error to avoid rejecting chain of promises
return Promises.resolve(null);
}
});
return chainActionsRecursively(derivedErrorSafePromise, iterator, breakOnFail);
}
/**
* Returns promise returned by PromisableAction or already resolved promise for non-PromisableAction.
*/
private Promise<Void> promiseAction(final Action action, final ActionEvent event) {
if (action instanceof PromisableAction) {
return ((PromisableAction)action).promise(event);
} else {
action.actionPerformed(event);
return Promises.resolve(null);
}
}
public Action getParentGroup(final String groupId,
final String actionName,
final String pluginId) {
if (groupId == null || groupId.length() == 0) {
reportActionError(pluginId, actionName + ": attribute \"group-id\" should be defined");
return null;
}
Action parentGroup = getActionImpl(groupId, true);
if (parentGroup == null) {
reportActionError(pluginId, actionName + ": group with id \"" + groupId +
"\" isn't registered; action will be added to the \"Other\" group");
parentGroup = getActionImpl(IdeActions.GROUP_OTHER_MENU, true);
}
if (!(parentGroup instanceof DefaultActionGroup)) {
reportActionError(pluginId, actionName + ": group with id \"" + groupId + "\" should be instance of " +
DefaultActionGroup.class.getName() +
" but was " + parentGroup.getClass());
return null;
}
return parentGroup;
}
@Override
public void registerAction(String actionId, Action action, String pluginId) {
if (myId2Action.containsKey(actionId)) {
reportActionError(pluginId, "action with the ID \"" + actionId + "\" was already registered. Action being registered is " +
action.toString() +
"; Registered action is " +
myId2Action.get(actionId) + pluginId);
return;
}
if (myAction2Id.containsKey(action)) {
reportActionError(pluginId, "action was already registered for another ID. ID is " + myAction2Id.get(action) +
pluginId);
return;
}
myId2Action.put(actionId, action);
myId2Index.put(actionId, myRegisteredActionsCount++);
myAction2Id.put(action, actionId);
if (pluginId != null && !(action instanceof ActionGroup)) {
Set<String> pluginActionIds = myPlugin2Id.get(pluginId);
if (pluginActionIds == null) {
pluginActionIds = new HashSet<>();
myPlugin2Id.put(pluginId, pluginActionIds);
}
pluginActionIds.add(actionId);
}
}
@Override
public void registerAction(String actionId, Action action) {
registerAction(actionId, action, null);
}
@Override
public void unregisterAction(String actionId) {
if (!myId2Action.containsKey(actionId)) {
Log.debug(getClass(), "action with ID " + actionId + " wasn't registered");
return;
}
Action oldValue = (Action)myId2Action.remove(actionId);
myAction2Id.remove(oldValue);
myId2Index.remove(actionId);
for (Entry<String, Set<String>> entry : myPlugin2Id.entrySet()) {
final Set<String> pluginActions = entry.getValue();
if (pluginActions != null) {
pluginActions.remove(actionId);
}
}
}
public Comparator<String> getRegistrationOrderComparator() {
return new Comparator<String>() {
public int compare(String id1, String id2) {
return myId2Index.get(id1) - myId2Index.get(id2);
}
};
}
public String[] getPluginActions(String pluginName) {
if (myPlugin2Id.containsKey(pluginName)) {
final Set<String> pluginActions = myPlugin2Id.get(pluginName);
return pluginActions.toArray(new String[pluginActions.size()]);
}
return EMPTY_ARRAY;
}
public Set<String> getActionIds() {
return new HashSet<>(myId2Action.keySet());
}
}