package org.netbeans.gradle.project.view;
import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import org.jtrim.property.MutableProperty;
import org.jtrim.property.PropertySource;
import org.jtrim.property.swing.AutoDisplayState;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.gradle.model.GradleTaskID;
import org.netbeans.gradle.project.NbGradleProject;
import org.netbeans.gradle.project.NbStrings;
import org.netbeans.gradle.project.api.nodes.GradleActionType;
import org.netbeans.gradle.project.api.nodes.GradleProjectAction;
import org.netbeans.gradle.project.api.nodes.GradleProjectContextActions;
import org.netbeans.gradle.project.api.task.CustomCommandActions;
import org.netbeans.gradle.project.api.task.GradleCommandTemplate;
import org.netbeans.gradle.project.extensions.NbGradleExtensionRef;
import org.netbeans.gradle.project.model.NbGradleModel;
import org.netbeans.gradle.project.properties.NbGradleCommonProperties;
import org.netbeans.gradle.project.properties.PredefinedTask;
import org.netbeans.gradle.project.properties.standard.PredefinedTasks;
import org.netbeans.gradle.project.properties.ui.AddNewTaskPanel;
import org.netbeans.gradle.project.tasks.vars.StringResolver;
import org.netbeans.gradle.project.tasks.vars.StringResolvers;
import org.netbeans.gradle.project.util.StringUtils;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.ui.support.CommonProjectActions;
import org.netbeans.spi.project.ui.support.ProjectSensitiveActions;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.util.Lookup;
import org.openide.util.Utilities;
import org.openide.util.actions.Presenter;
import org.openide.util.lookup.implspi.NamedServicesProvider;
public final class ProjectContextActionProvider implements ContextActionProvider {
private final NbGradleProject project;
public ProjectContextActionProvider(NbGradleProject project) {
ExceptionHelper.checkNotNullArgument(project, "project");
this.project = project;
}
@Override
public Action[] getActions() {
TasksActionMenu tasksAction = new TasksActionMenu(project);
CustomTasksActionMenu customTasksAction = new CustomTasksActionMenu(project);
List<Action> projectActions = new ArrayList<>(32);
projectActions.add(CommonProjectActions.newFileAction());
projectActions.add(null);
projectActions.add(createProjectAction(
ActionProvider.COMMAND_RUN,
NbStrings.getRunCommandCaption(true)));
projectActions.add(createProjectAction(
ActionProvider.COMMAND_DEBUG,
NbStrings.getDebugCommandCaption(true)));
projectActions.add(null);
projectActions.add(createProjectAction(
ActionProvider.COMMAND_BUILD,
NbStrings.getBuildCommandCaption(true)));
projectActions.add(createProjectAction(
ActionProvider.COMMAND_TEST,
NbStrings.getTestCommandCaption(true)));
projectActions.add(createProjectAction(
ActionProvider.COMMAND_CLEAN,
NbStrings.getCleanCommandCaption(true)));
projectActions.add(createProjectAction(
ActionProvider.COMMAND_REBUILD,
NbStrings.getRebuildCommandCaption()));
ExtensionActions extActions = getExtensionActions();
projectActions.addAll(extActions.getBuildActions());
projectActions.add(customTasksAction);
projectActions.add(tasksAction);
projectActions.add(null);
tryAddActionObj("Actions/Project/org-netbeans-modules-project-ui-problems-BrokenProjectActionFactory.instance", projectActions);
projectActions.add(createProjectAction(
GradleActionProvider.COMMAND_RELOAD,
NbStrings.getReloadCommandCaption(true)));
// Add the commented code below to provide a "Refresh project node" action.
// It was removed because it confused many, users can't easily distinguish it from "Reload project".
// projectActions.add(NodeUtils.getRefreshNodeAction(this, NbStrings.getRefreshNodeCommandCaption()));
projectActions.add(createProjectAction(
GradleActionProvider.COMMAND_SET_AS_MAIN_PROJECT,
NbStrings.getSetAsMainCaption()));
projectActions.addAll(extActions.getProjectManagementActions());
projectActions.add(CommonProjectActions.closeProjectAction());
projectActions.add(null);
projectActions.add(new DeleteProjectAction(project));
projectActions.add(null);
tryAddActionObj("Actions/Edit/org-openide-actions-FindAction.instance", projectActions);
projectActions.addAll(Utilities.actionsForPath("Projects/Actions"));
projectActions.add(null);
projectActions.add(CommonProjectActions.customizeProjectAction());
return projectActions.toArray(new Action[projectActions.size()]);
}
private ExtensionActions getExtensionActions() {
ExtensionActions result = new ExtensionActions();
for (NbGradleExtensionRef extensionRef: project.getExtensions().getExtensionRefs()) {
ExtensionActions actions = getActionsOfExtension(extensionRef);
result.mergeActions(actions);
}
if (!result.getBuildActions().isEmpty()) {
result.addActionSeparator(GradleActionType.BUILD_ACTION);
}
return result;
}
private static ExtensionActions getActionsOfExtension(NbGradleExtensionRef extension) {
Lookup extensionLookup = extension.getExtensionLookup();
Collection<? extends GradleProjectContextActions> actionQueries
= extensionLookup.lookupAll(GradleProjectContextActions.class);
ExtensionActions result = new ExtensionActions();
for (GradleProjectContextActions actionQuery: actionQueries) {
result.addAllActions(trimNulls(actionQuery.getContextActions()));
}
return result;
}
private static <T> List<T> trimNulls(List<T> list) {
int firstNonNullIndex = 0;
for (T element: list) {
if (element != null) {
break;
}
firstNonNullIndex++;
}
int afterLastNonNullIndex = list.size();
ListIterator<T> backItr = list.listIterator(afterLastNonNullIndex);
while (backItr.hasPrevious()) {
T element = backItr.previous();
if (element != null) {
break;
}
afterLastNonNullIndex--;
}
return list.subList(firstNonNullIndex, afterLastNonNullIndex);
}
private static void executeCommandTemplate(
NbGradleProject project,
GradleCommandTemplate command) {
CustomCommandActions actions = command.isBlocking()
? CustomCommandActions.OTHER
: CustomCommandActions.BUILD;
project.getGradleCommandExecutor().executeCommand(command, actions);
}
private static void tryAddActionObj(String objectPath, List<? super Action> actionList) {
Action action = NamedServicesProvider.getConfigObject(objectPath, Action.class);
if (action != null) {
actionList.add(action);
}
}
private static Action createProjectAction(String command, String label) {
return ProjectSensitiveActions.projectCommandAction(command, label, null);
}
@SuppressWarnings("serial") // don't care about serialization
private static class CustomTasksActionMenu extends AbstractAction implements Presenter.Popup {
private final NbGradleProject project;
private JMenu cachedMenu;
public CustomTasksActionMenu(NbGradleProject project) {
this.project = project;
this.cachedMenu = null;
}
@Override
public JMenuItem getPopupPresenter() {
if (cachedMenu == null) {
cachedMenu = createMenu();
}
return cachedMenu;
}
private JMenu createMenu() {
JMenu menu = new JMenu(NbStrings.getCustomTasksCommandCaption());
final CustomTasksMenuBuilder builder = new CustomTasksMenuBuilder(project, menu);
menu.addMenuListener(new MenuListener() {
@Override
public void menuSelected(MenuEvent e) {
builder.updateMenuContent();
}
@Override
public void menuDeselected(MenuEvent e) {
}
@Override
public void menuCanceled(MenuEvent e) {
}
});
return menu;
}
@Override
public void actionPerformed(ActionEvent e) {
}
}
private static class CustomTasksMenuBuilder {
private final NbGradleProject project;
private final JMenu menu;
private PredefinedTasks lastUsedTasks;
private NbGradleModel lastUsedModule;
public CustomTasksMenuBuilder(NbGradleProject project, JMenu menu) {
ExceptionHelper.checkNotNullArgument(project, "project");
ExceptionHelper.checkNotNullArgument(menu, "menu");
this.project = project;
this.menu = menu;
this.lastUsedTasks = null;
this.lastUsedModule = null;
}
public void updateMenuContent() {
PredefinedTasks commonTasks = project.getCommonProperties().customTasks().getActiveValue();
NbGradleModel mainModule = project.currentModel().getValue();
if (lastUsedTasks == commonTasks && lastUsedModule == mainModule) {
return;
}
lastUsedTasks = commonTasks;
lastUsedModule = mainModule;
List<PredefinedTask> commonTasksList = new ArrayList<>(commonTasks.getTasks());
Collections.sort(commonTasksList, new Comparator<PredefinedTask>() {
@Override
public int compare(PredefinedTask o1, PredefinedTask o2) {
String name1 = o1.getDisplayName();
String name2 = o2.getDisplayName();
return StringUtils.STR_CMP.compare(name1, name2);
}
});
boolean hasCustomTasks = false;
menu.removeAll();
StringResolver taskNameResolver = StringResolvers
.getDefaultResolverSelector()
.getProjectResolver(project, Lookup.EMPTY);
for (final PredefinedTask task: commonTasksList) {
if (!task.isTasksExistsIfRequired(project, taskNameResolver)) {
continue;
}
JMenuItem menuItem = new JMenuItem(task.getDisplayName());
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
executeCommandTemplate(project, task.toCommandTemplate());
}
});
menu.add(menuItem);
hasCustomTasks = true;
}
if (hasCustomTasks) {
menu.addSeparator();
}
menu.add(new CustomTaskAction(project));
}
}
@SuppressWarnings("serial") // don't care about serialization
private static class CustomTaskAction extends AbstractAction {
private final NbGradleProject project;
public CustomTaskAction(NbGradleProject project) {
super(NbStrings.getCustomTasksCommandCaption());
this.project = project;
}
private PredefinedTask tryCreateTaskDef(
CustomActionPanel actionPanel,
String displayName,
boolean tasksMustExist) {
actionPanel.setTasksMustExist(tasksMustExist);
return actionPanel.tryGetPredefinedTask(displayName);
}
private String doSaveTask(CustomActionPanel actionPanel) {
AddNewTaskPanel panel = new AddNewTaskPanel();
DialogDescriptor dlgDescriptor = new DialogDescriptor(
panel,
NbStrings.getAddNewTaskDlgTitle(),
true,
new Object[]{DialogDescriptor.OK_OPTION, DialogDescriptor.CANCEL_OPTION},
DialogDescriptor.OK_OPTION,
DialogDescriptor.BOTTOM_ALIGN,
null,
null);
Dialog dlg = DialogDisplayer.getDefault().createDialog(dlgDescriptor);
dlg.pack();
dlg.setVisible(true);
if (dlgDescriptor.getValue() != DialogDescriptor.OK_OPTION) {
return null;
}
String displayName = panel.getDisplayName();
if (displayName.isEmpty()) {
return null;
}
PredefinedTask newTaskDef = tryCreateTaskDef(actionPanel, displayName, true);
if (newTaskDef == null) {
return null;
}
if (!newTaskDef.isTasksExistsIfRequired(project, Lookup.EMPTY)) {
newTaskDef = tryCreateTaskDef(actionPanel, displayName, false);
}
addNewCommonTaskTask(newTaskDef);
return displayName;
}
private void addNewCommonTaskTask(final PredefinedTask newTaskDef) {
NbGradleCommonProperties commonProperties = project.getCommonProperties();
MutableProperty<PredefinedTasks> commonTasks = commonProperties.customTasks().getForActiveProfile();
List<PredefinedTask> currentTasks = commonTasks.getValue().getTasks();
List<PredefinedTask> newTasks = new ArrayList<>(currentTasks.size() + 1);
newTasks.addAll(currentTasks);
newTasks.add(newTaskDef);
commonTasks.setValue(new PredefinedTasks(newTasks));
}
@Override
public void actionPerformed(ActionEvent e) {
CustomActionPanel panel = new CustomActionPanel();
JButton executeButton = new JButton(NbStrings.getExecuteLabel());
JButton saveAndExecuteButton = new JButton(NbStrings.getSaveAndExecuteLabel());
final DialogDescriptor dlgDescriptor = new DialogDescriptor(
panel,
NbStrings.getCustomTaskDlgTitle(),
true,
new Object[]{executeButton, saveAndExecuteButton, DialogDescriptor.CANCEL_OPTION},
executeButton,
DialogDescriptor.BOTTOM_ALIGN,
null,
null);
PropertySource<Boolean> validInput = panel.validInput();
AutoDisplayState.addSwingStateListener(validInput,
AutoDisplayState.componentDisabler(executeButton, saveAndExecuteButton));
dlgDescriptor.setValid(validInput.getValue());
Dialog dlg = DialogDisplayer.getDefault().createDialog(dlgDescriptor);
dlg.setVisible(true);
String displayName;
boolean doExecute = false;
boolean okToClose;
do {
displayName = null;
okToClose = true;
Object selectedButton = dlgDescriptor.getValue();
if (saveAndExecuteButton == selectedButton) {
displayName = doSaveTask(panel);
okToClose = displayName != null;
if (!okToClose) {
dlg.setVisible(true);
}
doExecute = true;
}
else if (executeButton == selectedButton) {
doExecute = true;
}
} while (!okToClose);
if (doExecute) {
final GradleCommandTemplate commandTemplate = panel.tryGetGradleCommand(displayName);
if (commandTemplate != null) {
executeCommandTemplate(project, commandTemplate);
}
}
}
}
@SuppressWarnings("serial") // don't care about serialization
private static class TasksActionMenu extends AbstractAction implements Presenter.Popup {
private final NbGradleProject project;
private JMenu cachedMenu;
public TasksActionMenu(NbGradleProject project) {
this.project = project;
this.cachedMenu = null;
}
@Override
public JMenuItem getPopupPresenter() {
if (cachedMenu == null) {
cachedMenu = createMenu();
}
return cachedMenu;
}
private JMenu createMenu() {
JMenu menu = new JMenu(NbStrings.getTasksMenuCaption());
final TasksMenuBuilder builder = new TasksMenuBuilder(project, menu);
menu.addMenuListener(new MenuListener() {
@Override
public void menuSelected(MenuEvent e) {
builder.updateMenuContent();
}
@Override
public void menuDeselected(MenuEvent e) {
}
@Override
public void menuCanceled(MenuEvent e) {
}
});
return menu;
}
@Override
public void actionPerformed(ActionEvent e) {
}
}
private static class TasksMenuBuilder {
private final NbGradleProject project;
private final JMenu menu;
private NbGradleModel lastUsedModel;
public TasksMenuBuilder(NbGradleProject project, JMenu menu) {
ExceptionHelper.checkNotNullArgument(project, "project");
ExceptionHelper.checkNotNullArgument(menu, "menu");
this.project = project;
this.menu = menu;
this.lastUsedModel = null;
}
private void addToMenu(JMenu rootMenu, List<GradleTaskTree> rootNodes) {
for (GradleTaskTree root: rootNodes) {
List<GradleTaskTree> children = root.getChildren();
JMenuItem toAdd;
if (children.isEmpty()) {
toAdd = new JMenuItem(root.getCaption());
}
else {
JMenu subMenu = new JMenu(root.getCaption());
toAdd = subMenu;
addToMenu(subMenu, children);
}
final GradleTaskID taskID = root.getTaskID();
if (taskID != null) {
toAdd.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GradleCommandTemplate.Builder command
= new GradleCommandTemplate.Builder("", Arrays.asList(taskID.getFullName()));
executeCommandTemplate(project, command.create());
}
});
}
rootMenu.add(toAdd);
}
}
public void updateMenuContent() {
NbGradleModel projectModel = project.currentModel().getValue();
if (lastUsedModel == projectModel) {
return;
}
lastUsedModel = projectModel;
Collection<GradleTaskID> tasks = projectModel.getMainProject().getTasks();
menu.removeAll();
addToMenu(menu, GradleTaskTree.createTaskTree(tasks));
}
}
private static final class ExtensionActions {
private final List<Action> buildActions;
private final List<Action> projectManagementActions;
private GradleActionType lastActionType;
public ExtensionActions() {
this.buildActions = new ArrayList<>();
this.projectManagementActions = new ArrayList<>();
this.lastActionType = GradleActionType.BUILD_ACTION;
}
public List<Action> getBuildActions() {
return buildActions;
}
public List<Action> getProjectManagementActions() {
return projectManagementActions;
}
private List<Action> getListForActionKind(GradleActionType actionType) {
switch (actionType) {
case BUILD_ACTION:
return buildActions;
case PROJECT_MANAGEMENT_ACTION:
return projectManagementActions;
default:
throw new AssertionError(actionType.name());
}
}
private void addActionSeparator(GradleActionType actionType) {
getListForActionKind(actionType).add(null);
}
private void addAction(GradleActionType actionType, Action action) {
lastActionType = actionType;
getListForActionKind(actionType).add(action);
}
private GradleActionType getActionTypeForAction(Action action) {
if (action == null) {
return lastActionType;
}
GradleProjectAction annotation = action.getClass().getAnnotation(GradleProjectAction.class);
return annotation != null
? annotation.value()
: GradleActionType.BUILD_ACTION;
}
public void addAction(Action action) {
addAction(getActionTypeForAction(action), action);
}
public void addAllActions(Collection<? extends Action> actions) {
for (Action action: actions) {
addAction(action);
}
}
public void mergeActions(ExtensionActions actions) {
if (!actions.buildActions.isEmpty()) {
buildActions.add(null);
buildActions.addAll(actions.buildActions);
}
projectManagementActions.addAll(actions.projectManagementActions);
}
}
}