/*******************************************************************************
* 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.api.action;
import org.eclipse.che.ide.api.constraints.Anchor;
import org.eclipse.che.ide.api.constraints.Constraints;
import org.eclipse.che.ide.util.loging.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A default implementation of {@link ActionGroup}. Provides the ability
* to add children actions and separators between them. In most of the
* cases you will be using this implementation but note that there are
* cases where children are determined
* on rules different than just positional constraints, that's when you need
* to implement your own <code>ActionGroup</code>.
*
* @author Evgen Vidolob
* @author Dmitry Shnurenko
*/
public class DefaultActionGroup extends ActionGroup {
private static final Action[] EMPTY_ACTIONS = new Action[0];
/** Contains actions */
private final List<Action> actionList = new ArrayList<>();
/** Contains constraints */
private final List<Constraints> constraintsList = new ArrayList<>();
/** Contains instances of sorted Actions */
private Action[] sortedActions;
private ActionManager actionManager;
//TODO: think about removing this field
private boolean needSorting = false;
public DefaultActionGroup(ActionManager actionManager) {
this(null, false, actionManager);
}
/**
* Creates an action group containing the specified actions.
*
* @param actions
* the actions to add to the group
*/
public DefaultActionGroup(ActionManager actionManager, Action... actions) {
this(null, false, actionManager);
for (Action action : actions) {
add(action);
}
}
public DefaultActionGroup(String shortName, boolean popup, ActionManager actionManager) {
super(shortName, popup);
this.actionManager = actionManager;
}
private static int findIndex(String actionId, List<Action> actions, ActionManager actionManager) {
for (int i = 0; i < actions.size(); i++) {
Action action = actions.get(i);
if (action != null) {
String id = actionManager.getId(action);
if (id != null && id.equals(actionId)) {
return i;
}
}
}
return -1;
}
/**
* Adds the specified action to the tail.
*
* @param action
* Action to be added
* @param actionManager
* ActionManager instance
*/
public final void add(Action action, ActionManager actionManager) {
add(action, Constraints.LAST, actionManager);
}
public final void add(Action action) {
addAction(action, Constraints.LAST);
}
public final void addAction(Action action) {
addAction(action, Constraints.LAST);
}
/** Adds a separator to the tail. */
public final void addSeparator() {
add(Separator.getInstance());
}
public final void add(Action action, Constraints constraint) {
add(action, constraint, actionManager);
}
public final void addAction(Action action, Constraints constraint) {
addAction(action, constraint, actionManager);
}
/**
* Adds the specified action with the specified constraint.
*
* @param action
* Action to be added; cannot be null
* @param constraint
* Constraint to be used for determining action's position; cannot be null
* @throws IllegalArgumentException
* in case when:
* <li>action is null
* <li>constraint is null
* <li>action is already in the group
*/
public final void add(Action action, Constraints constraint, ActionManager actionManager) {
addAction(action, constraint, actionManager);
}
public final void addAction(Action action, Constraints constraint, ActionManager actionManager) {
if (action == this) {
throw new IllegalArgumentException("Cannot add a group to itself");
}
// Check that action isn't already registered
if (!(action instanceof Separator)) {
for (Action actionInList : actionList) {
if (action.equals(actionInList)) {
Log.error(getClass(), "Can not add an action twice: " + action);
return;
}
}
}
actionList.add(action);
constraintsList.add(constraint);
needSorting = true;
}
/**
* Removes specified action from group.
*
* @param actionToRemove
* Action to be removed
*/
public final void remove(Action actionToRemove) {
int index;
for (Action action : actionList) {
if (action.equals(actionToRemove)) {
index = actionList.indexOf(action);
actionList.remove(action);
constraintsList.remove(index);
needSorting = true;
return;
}
}
}
/** Removes all children actions (separators as well) from the group. */
public final void removeAll() {
actionList.clear();
constraintsList.clear();
needSorting = true;
}
/**
* Returns group's children in the order determined by constraints.
*
* @param e
* not used
* @return An array of children actions
*/
@Override
public final Action[] getChildren(ActionEvent e) {
if (needSorting) {
sortedActions = getSortedActions();
needSorting = false;
}
return sortedActions == null ? EMPTY_ACTIONS : sortedActions;
}
/**
* Sorts actions depending on their constraints and their input order.
*
* @return An array of sorted actions
*/
//TODO: to complicate
private Action[] getSortedActions() {
List<Action> result = new ArrayList<>();
Map<Action, Constraints> unsortedMap = new LinkedHashMap<>();
for (int i = 0; i < actionList.size(); i++) {
Action action = actionList.get(i);
Constraints constraints = constraintsList.get(i);
// if action is added to result list, it needs to call
// checkUnsorted method to look for another actions, that must be
// before or after this action
if (constraints.myAnchor.equals(Anchor.FIRST)) {
result.add(0, action);
checkUnsorted(unsortedMap, action, result);
} else if (constraints.myAnchor.equals(Anchor.LAST)) {
result.add(action);
checkUnsorted(unsortedMap, action, result);
} else {
// find related action in result list, if found, add action
// before or after it. If not, add to unsorted map
int index = findIndex(constraints.relativeId, result, actionManager);
if (index == -1) {
unsortedMap.put(action, constraints);
} else {
if (constraints.myAnchor.equals(Anchor.BEFORE)) {
result.add(index, action);
checkUnsorted(unsortedMap, action, result);
} else if (constraints.myAnchor.equals(Anchor.AFTER)) {
result.add(index + 1, action);
checkUnsorted(unsortedMap, action, result);
}
}
}
}
// append left unsorted actions to the end
result.addAll(unsortedMap.keySet());
return result.toArray(new Action[result.size()]);
}
/**
* This method checks unsorted map for actions, that depend
* on action, received in parameter. If found ones, adds it
*
* @param unsortedMap
* - map with unsorted actions
* @param action
* - action, that is a condition for actions in unsorted list
* @param result
* - result list
*/
private void checkUnsorted(Map<Action, Constraints> unsortedMap,
Action action,
List<Action> result) {
Iterator<Map.Entry<Action, Constraints>> itr = unsortedMap.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<Action, Constraints> entry = itr.next();
String actionId = actionManager.getId(action);
Action relatedAction = entry.getKey();
Constraints relatedConstraints = entry.getValue();
// if dependant action constraints match depends on our action
// add it to result and remove from unsorted list
if (relatedConstraints.relativeId.equals(actionId)) {
if (relatedConstraints.myAnchor.equals(Anchor.BEFORE)) {
result.add(result.indexOf(action), relatedAction);
} else if (relatedConstraints.myAnchor.equals(Anchor.AFTER)) {
result.add(result.indexOf(action) + 1, relatedAction);
}
itr.remove();
// recursive call of this method, but now passing the 'relatedAction'
// to find another actions, that related to 'relatedAction
checkUnsorted(unsortedMap, relatedAction, result);
}
}
}
/**
* Returns the number of contained children (including separators).
*
* @return number of children in the group
*/
public final int getChildrenCount() {
return actionList.size();
}
public final Action[] getChildActionsOrStubs() {
return sortedActions == null ? EMPTY_ACTIONS : sortedActions;
}
public final void addAll(ActionGroup group) {
for (Action each : group.getChildren(null)) {
add(each);
}
}
public final void addAll(Collection<Action> actionList) {
for (Action each : actionList) {
add(each);
}
}
public final void addAll(Action... actions) {
for (Action each : actions) {
add(each);
}
}
public void addSeparator(String separatorText) {
add(new Separator(separatorText));
}
}