org.eclipse.che.plugin.pullrequest.client.parts.contribute.ContributePartPresenter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.plugin.pullrequest.client.parts.contribute.ContributePartPresenter.java

Source

/*******************************************************************************
 * 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.plugin.pullrequest.client.parts.contribute;

import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
import org.eclipse.che.plugin.pullrequest.client.events.ContextInvalidatedEvent;
import org.eclipse.che.plugin.pullrequest.client.events.ContextInvalidatedHandler;
import org.eclipse.che.plugin.pullrequest.client.events.ContextPropertyChangeEvent;
import org.eclipse.che.plugin.pullrequest.client.events.ContextPropertyChangeHandler;
import org.eclipse.che.plugin.pullrequest.client.events.CurrentContextChangedEvent;
import org.eclipse.che.plugin.pullrequest.client.events.CurrentContextChangedHandler;
import org.eclipse.che.plugin.pullrequest.client.events.StepEvent;
import org.eclipse.che.plugin.pullrequest.client.events.StepHandler;
import org.eclipse.che.plugin.pullrequest.client.steps.CommitWorkingTreeStep;
import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowStatus;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;

import org.eclipse.che.api.git.shared.Branch;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.dialogs.CancelCallback;
import org.eclipse.che.ide.api.dialogs.DialogFactory;
import org.eclipse.che.ide.api.dialogs.InputCallback;
import org.eclipse.che.ide.api.dialogs.InputValidator;
import org.eclipse.che.ide.api.notification.NotificationManager;
import org.eclipse.che.ide.api.parts.WorkspaceAgent;
import org.eclipse.che.ide.api.parts.base.BasePresenter;
import org.eclipse.che.ide.api.resources.Project;
import org.eclipse.che.ide.util.loging.Log;

import javax.inject.Inject;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static com.google.common.base.Strings.nullToEmpty;
import static java.util.Arrays.asList;
import static org.eclipse.che.ide.api.constraints.Constraints.LAST;
import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
import static org.eclipse.che.ide.api.parts.PartStackType.TOOLING;

/**
 * Part for the contribution configuration.
 *
 * @author Kevin Pollet
 */
@Singleton
public class ContributePartPresenter extends BasePresenter implements ContributePartView.ActionDelegate,
        StepHandler, ContextPropertyChangeHandler, CurrentContextChangedHandler, ContextInvalidatedHandler {
    private final ContributePartView view;
    private final WorkspaceAgent workspaceAgent;
    private final ContributeMessages messages;
    private final WorkflowExecutor workflowExecutor;
    private final AppContext appContext;
    private final NotificationManager notificationManager;
    private final DialogFactory dialogFactory;
    private final Map<String, StagesProvider> stagesProviders;

    @Inject
    public ContributePartPresenter(final ContributePartView view, final ContributeMessages messages,
            final WorkspaceAgent workspaceAgent, final EventBus eventBus, final WorkflowExecutor workflow,
            final AppContext appContext, final NotificationManager notificationManager,
            final DialogFactory dialogFactory, final Map<String, StagesProvider> stagesProviders) {
        this.view = view;
        this.workspaceAgent = workspaceAgent;
        this.workflowExecutor = workflow;
        this.messages = messages;
        this.appContext = appContext;
        this.notificationManager = notificationManager;
        this.dialogFactory = dialogFactory;
        this.stagesProviders = stagesProviders;

        this.view.setDelegate(this);

        view.addContributionTitleChangedHandler(new TextChangedHandler() {
            @Override
            public void onTextChanged(String newText) {
                final Context curContext = workflowExecutor.getCurrentContext();
                curContext.getViewState().setContributionTitle(newText);
            }
        });

        view.addContributionCommentChangedHandler(new TextChangedHandler() {
            @Override
            public void onTextChanged(String newText) {
                final Context curContext = workflowExecutor.getCurrentContext();
                curContext.getViewState().setContributionComment(newText);
            }
        });

        view.addBranchChangedHandler(new TextChangedHandler() {
            @Override
            public void onTextChanged(String branchName) {
                final Context curContext = workflowExecutor.getCurrentContext();
                if (!branchName.equals(messages
                        .contributePartConfigureContributionSectionContributionBranchNameCreateNewItemText())
                        && !branchName.equals(curContext.getWorkBranchName())) {
                    checkoutBranch(curContext, branchName, false);
                }
            }
        });

        eventBus.addHandler(StepEvent.TYPE, this);
        eventBus.addHandler(ContextPropertyChangeEvent.TYPE, this);
        eventBus.addHandler(CurrentContextChangedEvent.TYPE, this);
        eventBus.addHandler(ContextInvalidatedEvent.TYPE, this);
    }

    public void open() {
        resetView();
        workspaceAgent.openPart(ContributePartPresenter.this, TOOLING, LAST);
    }

    public void remove() {
        workspaceAgent.removePart(ContributePartPresenter.this);
    }

    @Override
    public void onContribute() {
        final Context context = workflowExecutor.getCurrentContext();
        context.getViewState().setStatusMessage(null);
        context.getViewState().resetStages();

        updateView(context, new NewContributionPanelUpdate(), new StatusMessageUpdate(), new StatusSectionUpdate());

        // Extract configuration values and perform contribution
        if (isCurrentContext(context)) {
            context.getConfiguration().withContributionBranchName(view.getContributionBranchName())
                    .withContributionComment(view.getContributionComment())
                    .withContributionTitle(view.getContributionTitle());
        }

        if (context.isUpdateMode()) {
            workflowExecutor.updatePullRequest(context);
        } else {
            workflowExecutor.createPullRequest(context);
        }

        updateView(context, new ContributionButtonUpdate(messages));
    }

    private void restore(final Context context) {
        final List<ViewUpdate> updates = new ArrayList<>();
        // Repository panel updates
        updates.add(new RepositoryUrlUpdate());
        updates.add(new ClonedBranchUpdate());
        updates.add(new ProjectNameUpdate());
        // All the other panels are available only if the mode is different from INITIALIZING
        // All the other panels are hidden by the #open method, which is called before initialization
        if (context.getStatus() != WorkflowStatus.INITIALIZING) {
            // Configuration panel updates
            updates.add(new WorkBranchUpdate());
            updates.add(new ContributionTitleUpdate());
            updates.add(new ContributionCommentUpdate());
            // Contribution button update
            updates.add(new ContributionButtonUpdate(messages));
            // Status panel updates
            updates.add(new StatusSectionUpdate());
            updates.add(new StatusMessageUpdate());
            // New contribution panel updates
            updates.add(new NewContributionPanelUpdate());
        }
        updateView(context, updates);
    }

    /** Continuously resets the view state as long as current project is not changed. */
    private void resetView() {
        final Project project = appContext.getRootProject();
        final String projectName = project != null ? project.getName() : null;
        if (!isCurrentProject(projectName))
            return;
        view.setRepositoryUrl("");

        if (!isCurrentProject(projectName))
            return;
        view.setContributeToBranch("");

        if (!isCurrentProject(projectName))
            return;
        view.setContributionBranchName("");

        if (!isCurrentProject(projectName))
            return;
        view.setContributionBranchNameEnabled(true);

        if (!isCurrentProject(projectName))
            return;
        view.setContributionBranchNameList(Collections.<String>emptyList());

        if (!isCurrentProject(projectName))
            return;
        view.setContributionTitle("");

        if (!isCurrentProject(projectName))
            return;
        view.setProjectName("");

        if (!isCurrentProject(projectName))
            return;
        view.setContributionTitleEnabled(true);

        if (!isCurrentProject(projectName))
            return;
        view.setContributionComment("");

        if (!isCurrentProject(projectName))
            return;
        view.setContributionCommentEnabled(true);

        if (!isCurrentProject(projectName))
            return;
        view.setContributeButtonText(messages.contributePartConfigureContributionSectionButtonContributeText());

        if (!isCurrentProject(projectName))
            return;
        view.hideStatusSection();

        if (!isCurrentProject(projectName))
            return;
        view.hideNewContributionSection();

        if (!isCurrentProject(projectName))
            return;
        updateControls();
    }

    @Override
    public void onOpenPullRequestOnVcsHost() {
        final Context context = workflowExecutor.getCurrentContext();

        Window.open(context.getVcsHostingService().makePullRequestUrl(context.getUpstreamRepositoryOwner(),
                context.getUpstreamRepositoryName(), context.getPullRequestIssueNumber()), "", "");
    }

    @Override
    public void onNewContribution() {
        final Context context = workflowExecutor.getCurrentContext();
        context.getVcsService().checkoutBranch(context.getProject(), context.getContributeToBranchName(), false,
                new AsyncCallback<String>() {
                    @Override
                    public void onFailure(final Throwable exception) {
                        notificationManager.notify(exception.getMessage(), FAIL, FLOAT_MODE);
                    }

                    @Override
                    public void onSuccess(final String branchName) {
                        resetView();
                        workflowExecutor.invalidateContext(context.getProject());
                        workflowExecutor.init(context.getVcsHostingService(), context.getProject());
                    }
                });
    }

    @Override
    public void onRefreshContributionBranchNameList() {
        updateView(workflowExecutor.getCurrentContext(), new WorkBranchUpdate());
    }

    @Override
    public void onCreateNewBranch() {
        final Context context = workflowExecutor.getCurrentContext();
        dialogFactory
                .createInputDialog(messages.contributePartConfigureContributionDialogNewBranchTitle(),
                        messages.contributePartConfigureContributionDialogNewBranchLabel(),
                        new CreateNewBranchCallback(context), new CancelNewBranchCallback(context))
                .withValidator(new BranchNameValidator()).show();
    }

    @Override
    public void updateControls() {
        final String contributionTitle = view.getContributionTitle();

        boolean isValid = true;
        view.showContributionTitleError(false);

        if (contributionTitle == null || contributionTitle.trim().isEmpty()) {
            view.showContributionTitleError(true);
            isValid = false;
        }

        view.setContributeButtonEnabled(isValid);
    }

    @Override
    public void go(final AcceptsOneWidget container) {
        container.setWidget(view.asWidget());
    }

    @NotNull
    @Override
    public String getTitle() {
        return messages.contributePartTitle();
    }

    @Override
    public IsWidget getView() {
        return view;
    }

    @Nullable
    @Override
    public String getTitleToolTip() {
        return null;
    }

    @Override
    public int getSize() {
        return 350;
    }

    @Override
    public void onStepDone(final StepEvent event) {
        final Class<? extends Step> stepClass = event.getStep().getClass();
        final Context context = event.getContext();

        // if it is necessarily to display stages on this step
        if (getProvider(context).getDisplayStagesType(context) == stepClass) {
            context.getViewState().setStages(getProvidedStages(context));
            updateView(context, new StatusSectionUpdate());
        }

        // if current step is in list of provided stages types
        // then this step is done and view should be affected
        if (!context.getViewState().getStages().isEmpty()
                && getProvidedStepDoneTypes(context).contains(stepClass)) {
            context.getViewState().setStageDone(true);
            updateView(context, new DisplayCurrentStepResultUpdate(true));
        } else if (stepClass == WorkflowExecutor.ChangeContextStatusStep.class) {
            if (context.getStatus() == WorkflowStatus.READY_TO_UPDATE_PR) {
                final List<ViewUpdate> updates = new ArrayList<>();
                // Display status message
                final String message;
                if (context.getPreviousStatus() == WorkflowStatus.CREATING_PR) {
                    message = messages.contributePartStatusSectionContributionCreatedMessage();
                } else {
                    message = messages.contributePartStatusSectionContributionUpdatedMessage();
                }
                context.getViewState().setStatusMessage(message, false);
                updates.add(new StatusMessageUpdate());

                // Contribution button
                updates.add(new ContributionButtonUpdate(messages));

                // Config panel
                updates.add(new ContributionTitleUpdate());
                updates.add(new ContributionCommentUpdate());

                // New contribution panel
                updates.add(new NewContributionPanelUpdate());

                updateView(context, updates);
            }
        }
    }

    @Override
    public void onStepError(final StepEvent event) {
        final Step step = event.getStep();
        final Class<? extends Step> stepClass = step.getClass();
        final Context context = event.getContext();
        if (stepClass == CommitWorkingTreeStep.class) {
            if (!context.isUpdateMode()) {
                context.getViewState().resetStages();
                context.getViewState().setStatusMessage(null);
                updateView(context, new StatusSectionUpdate(), new StatusMessageUpdate(),
                        new NewContributionPanelUpdate(), new ContributionButtonUpdate(messages));
            } else {
                context.getViewState().resetStages();
                context.getViewState().setStatusMessage(event.getMessage(), true);
                updateView(context, new StatusSectionUpdate(), new StatusMessageUpdate(),
                        new ContributionButtonUpdate(messages));
            }
        } else if (getProvidedStepErrorTypes(context).contains(stepClass)) {
            context.getViewState().setStageDone(false);
            context.getViewState().setStatusMessage(event.getMessage(), true);
            updateView(context, new DisplayCurrentStepResultUpdate(false), new StatusMessageUpdate(),
                    new ContributionButtonUpdate(messages));
        } else {
            context.getViewState().resetStages();
            restore(context);
            Log.error(ContributePartPresenter.class, "Step error: ", event.getMessage());
        }
    }

    @Override
    public void onContextPropertyChange(final ContextPropertyChangeEvent event) {
        final Context context = event.getContext();

        switch (event.getContextProperty()) {
        case CONTRIBUTE_TO_BRANCH_NAME:
            updateView(context, new ClonedBranchUpdate());
            break;

        case WORK_BRANCH_NAME:
            updateView(context, new WorkBranchUpdate());
            break;

        case ORIGIN_REPOSITORY_NAME:
        case ORIGIN_REPOSITORY_OWNER:
            updateView(context, new RepositoryUrlUpdate());
            break;

        case PROJECT:
            updateView(context, new ProjectNameUpdate());
            break;

        default:
            // nothing to do
            break;
        }
    }

    @Override
    public void onContextChanged(final Context context) {
        restore(context);
    }

    private void updateView(final Context context, final ViewUpdate... updates) {
        updateView(context, asList(updates));
    }

    private void updateView(final Context context, final List<ViewUpdate> updates) {
        for (Iterator<ViewUpdate> it = updates.iterator(); it.hasNext() && isCurrentContext(context);) {
            it.next().update(view, context);
        }
    }

    private void updateView(final Context context, final ViewUpdate update) {
        if (isCurrentContext(context)) {
            update.update(view, context);
        }
    }

    @Override
    public void onContextInvalidated(Context context) {
        resetView();
    }

    /**
     * Defines a single update operation.
     * Single update operations are required as view is shared between multiple projects.
     * If context is switched view should not be updated with the updates related to the previous context.
     */
    private interface ViewUpdate {
        void update(final ContributePartView view, final Context context);
    }

    private static class DisplayCurrentStepResultUpdate implements ViewUpdate {
        private final boolean result;

        public DisplayCurrentStepResultUpdate(boolean result) {
            this.result = result;
        }

        @Override
        public void update(final ContributePartView view, final Context context) {
            view.setCurrentStatusStepStatus(result);
        }
    }

    private static class NewContributionPanelUpdate implements ViewUpdate {
        @Override
        public void update(final ContributePartView view, final Context context) {
            view.hideNewContributionSection();
            if (context.isUpdateMode()) {
                view.showNewContributionSection(context.getVcsHostingService().getName());
            }
        }
    }

    private static class StatusMessageUpdate implements ViewUpdate {
        @Override
        public void update(final ContributePartView view, final Context context) {
            view.hideStatusSectionMessage();
            final Context.ViewState.StatusMessage statusMessage = context.getViewState().getStatusMessage();
            if (statusMessage != null) {
                view.showStatusSectionMessage(statusMessage.getMessage(), statusMessage.isError());
            }
        }
    }

    private static class StatusSectionUpdate implements ViewUpdate {
        @Override
        public void update(ContributePartView view, Context context) {
            view.hideStatusSection();
            final List<Context.ViewState.Stage> stepStatuses = context.getViewState().getStages();
            if (stepStatuses.size() > 0) {
                final String[] names = context.getViewState().getStageNames()
                        .toArray(new String[stepStatuses.size()]);
                view.showStatusSection(names);
                for (Boolean stepStatus : context.getViewState().getStageValues()) {
                    if (stepStatus == null) {
                        break;
                    }
                    view.setCurrentStatusStepStatus(stepStatus);
                }
            } else if (context.getViewState().getStatusMessage() != null) {
                view.showStatusSection();
            }
        }
    }

    private static class ClonedBranchUpdate implements ViewUpdate {
        @Override
        public void update(final ContributePartView view, final Context context) {
            view.setContributeToBranch(nullToEmpty(context.getContributeToBranchName()));
        }
    }

    private static class ContributionButtonUpdate implements ViewUpdate {
        private final ContributeMessages messages;

        private ContributionButtonUpdate(final ContributeMessages messages) {
            this.messages = messages;
        }

        @Override
        public void update(final ContributePartView view, final Context context) {
            final boolean isEnabled = !nullToEmpty(context.getViewState().getContributionTitle()).isEmpty();
            final boolean isInProgress;
            final String buttonText;
            switch (context.getStatus()) {
            case UPDATING_PR:
                buttonText = messages.contributePartConfigureContributionSectionButtonContributeUpdateText();
                isInProgress = true;
                break;
            case READY_TO_UPDATE_PR:
                buttonText = messages.contributePartConfigureContributionSectionButtonContributeUpdateText();
                isInProgress = false;
                break;
            case CREATING_PR:
                buttonText = messages.contributePartConfigureContributionSectionButtonContributeText();
                isInProgress = true;
                break;
            case READY_TO_CREATE_PR:
                buttonText = messages.contributePartConfigureContributionSectionButtonContributeText();
                isInProgress = false;
                break;
            default:
                throw new IllegalStateException("Illegal workflow status " + context.getStatus());
            }
            view.setContributeButtonText(buttonText);
            view.setContributionProgressState(isInProgress);
            view.setContributeButtonEnabled(isEnabled);
        }
    }

    private class ContributionCommentUpdate implements ViewUpdate {
        @Override
        public void update(final ContributePartView view, final Context context) {
            view.setContributionComment(nullToEmpty(context.getViewState().getContributionComment()));
            view.setContributionCommentEnabled(context.getStatus() == WorkflowStatus.READY_TO_CREATE_PR);
        }
    }

    private static class ContributionTitleUpdate implements ViewUpdate {
        @Override
        public void update(final ContributePartView view, final Context context) {
            view.setContributionTitle(nullToEmpty(context.getViewState().getContributionTitle()));
            view.setContributionTitleEnabled(context.getStatus() == WorkflowStatus.READY_TO_CREATE_PR);
        }
    }

    private static class RepositoryUrlUpdate implements ViewUpdate {
        @Override
        public void update(final ContributePartView view, final Context context) {
            final String originRepositoryName = context.getOriginRepositoryName();
            final String originRepositoryOwner = context.getOriginRepositoryOwner();
            if (originRepositoryName != null && originRepositoryOwner != null) {
                view.setRepositoryUrl(context.getVcsHostingService().makeHttpRemoteUrl(originRepositoryOwner,
                        originRepositoryName));
            }
        }
    }

    private static class WorkBranchUpdate implements ViewUpdate {
        @Override
        public void update(final ContributePartView view, final Context context) {
            context.getVcsService().listLocalBranches(context.getProject(), new AsyncCallback<List<Branch>>() {
                @Override
                public void onFailure(final Throwable notUsed) {
                }

                @Override
                public void onSuccess(final List<Branch> branches) {
                    final List<String> branchNames = new ArrayList<>();
                    for (final Branch oneBranch : branches) {
                        branchNames.add(oneBranch.getDisplayName());
                    }
                    view.setContributionBranchNameList(branchNames);
                    view.setContributionBranchName(context.getWorkBranchName());
                }
            });
        }
    }

    private static class ProjectNameUpdate implements ViewUpdate {
        @Override
        public void update(final ContributePartView view, final Context context) {
            view.setProjectName(context.getProject().getName());
        }
    }

    private boolean isCurrentContext(final Context context) {
        final Project project = appContext.getRootProject();

        return project != null && Objects.equals(context.getProject().getName(), project.getName());
    }

    private boolean isCurrentProject(final String projectName) {
        final Project project = appContext.getRootProject();

        return project != null && Objects.equals(projectName, project.getName());
    }

    private StagesProvider getProvider(final Context context) {
        for (Map.Entry<String, StagesProvider> entry : stagesProviders.entrySet()) {
            if (entry.getKey().equals(context.getVcsHostingService().getName())) {
                return entry.getValue();
            }
        }
        throw new IllegalStateException("StagesProvider for VCS hosting service "
                + context.getVcsHostingService().getName() + " isn't registered");
    }

    private List<String> getProvidedStages(final Context context) {
        return getProvider(context).getStages(context);
    }

    private Set<Class<? extends Step>> getProvidedStepDoneTypes(final Context context) {
        return getProvider(context).getStepDoneTypes(context);
    }

    private Set<Class<? extends Step>> getProvidedStepErrorTypes(final Context context) {
        return getProvider(context).getStepErrorTypes(context);
    }

    private static class BranchNameValidator implements InputValidator {
        private static final Violation ERROR_WITH_NO_MESSAGE = new InputValidator.Violation() {
            @Nullable
            @Override
            public String getMessage() {
                return "";
            }

            @Nullable
            @Override
            public String getCorrectedValue() {
                return null;
            }
        };

        @Nullable
        @Override
        public Violation validate(final String branchName) {
            return branchName.matches("[0-9A-Za-z-]+") ? null : ERROR_WITH_NO_MESSAGE;
        }
    }

    private class CreateNewBranchCallback implements InputCallback {
        private final Context context;

        public CreateNewBranchCallback(final Context context) {
            this.context = context;
        }

        @Override
        public void accepted(final String branchName) {
            context.getVcsService().isLocalBranchWithName(context.getProject(), branchName,
                    new AsyncCallback<Boolean>() {
                        @Override
                        public void onFailure(final Throwable exception) {
                            notificationManager.notify(exception.getMessage(), FAIL, FLOAT_MODE);
                        }

                        @Override
                        public void onSuccess(final Boolean branchExists) {
                            if (branchExists) {
                                notificationManager.notify(messages
                                        .contributePartConfigureContributionDialogNewBranchErrorBranchExists(
                                                branchName),
                                        FAIL, FLOAT_MODE);

                            } else {
                                checkoutBranch(context, branchName, true);
                            }
                        }
                    });
        }
    }

    private void checkoutBranch(final Context context, final String branchName, final boolean createNew) {
        context.getVcsService().checkoutBranch(context.getProject(), branchName, createNew,
                new AsyncCallback<String>() {
                    @Override
                    public void onFailure(final Throwable exception) {
                        notificationManager.notify(exception.getLocalizedMessage(), FAIL, FLOAT_MODE);
                    }

                    @Override
                    public void onSuccess(final String notUsed) {
                        workflowExecutor.invalidateContext(context.getProject());
                        workflowExecutor.init(context.getVcsHostingService(), context.getProject());
                    }
                });
    }

    private class CancelNewBranchCallback implements CancelCallback {
        private final Context context;

        private CancelNewBranchCallback(final Context context) {
            this.context = context;
        }

        @Override
        public void cancelled() {
            updateView(context, new WorkBranchUpdate());
        }
    }
}