org.eclipse.mylyn.internal.gerrit.ui.editor.PatchSetSection.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.mylyn.internal.gerrit.ui.editor.PatchSetSection.java

Source

/*******************************************************************************
 * Copyright (c) 2010 Tasktop Technologies and others.
 * 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:
 *     Tasktop Technologies - initial API and implementation
 *     Sascha Scholz (SAP) - improvements
 *     Sam Davis - improvements for bug 383592
 *******************************************************************************/

package org.eclipse.mylyn.internal.gerrit.ui.editor;

import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareUI;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.egit.ui.internal.commit.CommitEditor;
import org.eclipse.egit.ui.internal.fetch.FetchGerritChangeWizard;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.mylyn.internal.gerrit.core.GerritCorePlugin;
import org.eclipse.mylyn.internal.gerrit.core.GerritTaskSchema;
import org.eclipse.mylyn.internal.gerrit.core.GerritUtil;
import org.eclipse.mylyn.internal.gerrit.core.ReviewItemCache;
import org.eclipse.mylyn.internal.gerrit.core.client.GerritChange;
import org.eclipse.mylyn.internal.gerrit.core.client.GerritPatchSetContent;
import org.eclipse.mylyn.internal.gerrit.core.client.compat.ChangeDetailX;
import org.eclipse.mylyn.internal.gerrit.core.egit.GerritToGitMapping;
import org.eclipse.mylyn.internal.gerrit.ui.GerritReviewBehavior;
import org.eclipse.mylyn.internal.gerrit.ui.GerritUiPlugin;
import org.eclipse.mylyn.internal.gerrit.ui.egit.EGitUiUtil;
import org.eclipse.mylyn.internal.gerrit.ui.operations.AbandonDialog;
import org.eclipse.mylyn.internal.gerrit.ui.operations.PublishDialog;
import org.eclipse.mylyn.internal.gerrit.ui.operations.RebaseDialog;
import org.eclipse.mylyn.internal.gerrit.ui.operations.RestoreDialog;
import org.eclipse.mylyn.internal.gerrit.ui.operations.SubmitDialog;
import org.eclipse.mylyn.internal.reviews.ui.compare.FileItemCompareEditorInput;
import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.reviews.core.model.IFileItem;
import org.eclipse.mylyn.reviews.core.model.IReviewItem;
import org.eclipse.mylyn.reviews.core.model.IReviewItemSet;
import org.eclipse.mylyn.reviews.internal.core.model.ReviewsPackage;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.ui.TasksUi;
import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.statushandlers.StatusManager;

import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.common.data.PatchSetPublishDetail;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;

/**
 * @author Steffen Pingel
 * @author Sascha Scholz
 */
public class PatchSetSection extends AbstractGerritSection {

    private class CompareAction extends Action {

        private final PatchSet base;

        private final PatchSet target;

        private final ChangeDetail changeDetail;

        public CompareAction(ChangeDetail changeDetail, PatchSet base, PatchSet target) {
            this.changeDetail = changeDetail;
            this.base = base;
            this.target = target;
        }

        public void fill(Menu menu) {
            MenuItem item = new MenuItem(menu, SWT.NONE);
            item.setText(NLS.bind("Compare with Patch Set {0}", base.getPatchSetId()));
            item.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    run();
                }
            });
        }

        @Override
        public void run() {
            doCompareWith(changeDetail, base, target);
        }

    }

    private Composite composite;

    private final List<Job> jobs;

    private FormToolkit toolkit;

    // XXX drafts added after the publish detail was refreshed from server
    private int addedDrafts;

    private final ReviewItemCache cache;

    public PatchSetSection() {
        setPartName("Patch Sets");
        this.jobs = new ArrayList<Job>();
        this.cache = new ReviewItemCache();
    }

    @Override
    public void dispose() {
        for (Job job : jobs) {
            job.cancel();
        }
        super.dispose();
    }

    public void updateTextClient(Section section, final PatchSetDetail patchSetDetail, boolean cachingInProgress) {
        String message;

        String time = DateFormat.getDateTimeInstance().format(patchSetDetail.getPatchSet().getCreatedOn());
        int numComments = getNumComments(patchSetDetail);
        if (numComments > 0) {
            message = NLS.bind("{0}, {1} Comments", time, numComments);
        } else {
            message = NLS.bind("{0}", time);
        }

        if (cachingInProgress) {
            message += " [Caching contents...]";
        }

        final Label textClientLabel = (Label) section.getTextClient();
        textClientLabel.setText("  " + message);
        textClientLabel.getParent().layout(true, true);
        //textClientLabel.setVisible(cachingInProgress || !section.isExpanded());
    }

    @Override
    public void initialize(AbstractTaskEditorPage taskEditorPage) {
        super.initialize(taskEditorPage);
    }

    private Composite createActions(final ChangeDetail changeDetail, final PatchSetDetail patchSetDetail,
            final PatchSetPublishDetail publishDetail, Composite composite) {
        Composite buttonComposite = new Composite(composite, SWT.NONE);
        RowLayout layout = new RowLayout();
        layout.center = true;
        layout.spacing = 10;
        buttonComposite.setLayout(layout);

        boolean canPublish = getTaskData().getAttributeMapper().getBooleanValue(
                getTaskData().getRoot().getAttribute(GerritTaskSchema.getDefault().CAN_PUBLISH.getKey()));
        boolean canSubmit = false;
        boolean canRebase = false;
        if (changeDetail.getCurrentActions() != null) {
            canSubmit = changeDetail.getCurrentActions().contains(ApprovalCategory.SUBMIT);
        } else if (changeDetail instanceof ChangeDetailX) {
            // Gerrit 2.2 and later
            canSubmit = ((ChangeDetailX) changeDetail).canSubmit();
            canRebase = ((ChangeDetailX) changeDetail).canRebase();
        }

        if (canPublish) {
            Button publishButton = toolkit.createButton(buttonComposite, "Publish Comments...", SWT.PUSH);
            publishButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    doPublish(publishDetail);
                }
            });
        }

        Button fetchButton = toolkit.createButton(buttonComposite, "Fetch...", SWT.PUSH);
        fetchButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                doFetch(changeDetail, patchSetDetail);
            }
        });

        final Composite compareComposite = toolkit.createComposite(buttonComposite);
        GridLayoutFactory.fillDefaults().numColumns(2).spacing(0, 0).applyTo(compareComposite);

        Button compareButton = toolkit.createButton(compareComposite, "Compare With Base", SWT.PUSH);
        compareButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                doCompareWith(changeDetail, null, patchSetDetail.getPatchSet());
            }
        });

        if (changeDetail.getPatchSets().size() > 1) {
            Button compareWithButton = toolkit.createButton(compareComposite, "", SWT.PUSH);
            GridDataFactory.fillDefaults().grab(false, true).applyTo(compareWithButton);
            compareWithButton.setImage(WorkbenchImages.getImage(IWorkbenchGraphicConstants.IMG_LCL_BUTTON_MENU));
            compareWithButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    showCompareMenu(compareComposite, changeDetail, patchSetDetail);
                }

                private void showCompareMenu(Composite compareComposite, ChangeDetail changeDetail,
                        PatchSetDetail patchSetDetail) {
                    Menu menu = new Menu(compareComposite);
                    Point p = compareComposite.getLocation();
                    p.y = p.y + compareComposite.getSize().y;
                    p = compareComposite.getParent().toDisplay(p);
                    fillCompareWithMenu(changeDetail, patchSetDetail, menu);
                    menu.setLocation(p);
                    menu.setVisible(true);
                }
            });
        }

        if (canRebase) {
            Button rebaseButton = toolkit.createButton(buttonComposite, "Rebase", SWT.PUSH);
            rebaseButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    doRebase(patchSetDetail.getPatchSet());
                }
            });
        }

        if (canSubmit) {
            Button submitButton = toolkit.createButton(buttonComposite, "Submit", SWT.PUSH);
            submitButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    doSubmit(patchSetDetail.getPatchSet());
                }
            });
        }

        if (changeDetail != null && changeDetail.isCurrentPatchSet(patchSetDetail)) {
            if (changeDetail.canAbandon()) {
                Button abondonButton = toolkit.createButton(buttonComposite, "Abandon...", SWT.PUSH);
                abondonButton.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        doAbandon(patchSetDetail.getPatchSet());
                    }
                });
            } else if (changeDetail.canRestore()) {
                Button restoreButton = toolkit.createButton(buttonComposite, "Restore...", SWT.PUSH);
                restoreButton.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        doRestore(patchSetDetail.getPatchSet());
                    }
                });
            }
        }
        return buttonComposite;
    }

    void fillCompareWithMenu(ChangeDetail changeDetail, PatchSetDetail patchSetDetail, Menu menu) {
        for (PatchSet patchSet : changeDetail.getPatchSets()) {
            if (patchSet.getPatchSetId() != patchSetDetail.getPatchSet().getPatchSetId()) {
                CompareAction action = new CompareAction(changeDetail, patchSet, patchSetDetail.getPatchSet());
                action.fill(menu);
            }
        }
    }

    private void createSubSection(final ChangeDetail changeDetail, final PatchSetDetail patchSetDetail,
            final PatchSetPublishDetail publishDetail, Section section) {
        int style = ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT
                | ExpandableComposite.LEFT_TEXT_CLIENT_ALIGNMENT;
        if (changeDetail.isCurrentPatchSet(patchSetDetail)) {
            style |= ExpandableComposite.EXPANDED;
        }
        final Section subSection = toolkit.createSection(composite, style);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(subSection);
        subSection.setText(NLS.bind("Patch Set {0}", patchSetDetail.getPatchSet().getId().get()));
        subSection.setTitleBarForeground(toolkit.getColors().getColor(IFormColors.TITLE));

        addTextClient(toolkit, subSection, "", false); //$NON-NLS-1$
        updateTextClient(subSection, patchSetDetail, false);

        if (subSection.isExpanded()) {
            createSubSectionContents(changeDetail, patchSetDetail, publishDetail, subSection);
        }
        subSection.addExpansionListener(new ExpansionAdapter() {
            @Override
            public void expansionStateChanged(ExpansionEvent e) {
                if (subSection.getClient() == null) {
                    createSubSectionContents(changeDetail, patchSetDetail, publishDetail, subSection);
                }
            }
        });
    }

    private int getNumComments(PatchSetDetail patchSetDetail) {
        int numComments = 0;
        for (Patch patch : patchSetDetail.getPatches()) {
            numComments += patch.getCommentCount();
        }
        return numComments;
    }

    private void subSectionExpanded(final ChangeDetail changeDetail, final PatchSetDetail patchSetDetail,
            final Section composite, final Viewer viewer) {
        updateTextClient(composite, patchSetDetail, true);

        final GetPatchSetContentJob job = new GetPatchSetContentJob(getTaskEditorPage().getTaskRepository(),
                patchSetDetail);
        job.addJobChangeListener(new JobChangeAdapter() {
            @Override
            public void done(final IJobChangeEvent event) {
                Display.getDefault().asyncExec(new Runnable() {
                    public void run() {
                        if (getControl() != null && !getControl().isDisposed()) {
                            if (event.getResult().isOK()) {
                                GerritPatchSetContent content = job.getPatchSetContent();
                                if (content != null && content.getPatchScriptByPatchKey() != null) {
                                    viewer.setInput(GerritUtil.createInput(changeDetail, content, cache));
                                }
                            }

                            updateTextClient(composite, patchSetDetail, false);
                            getTaskEditorPage().reflow();
                        }
                    }
                });
            }
        });
        jobs.add(job);
        job.schedule();
    }

    @Override
    protected Control createContent(FormToolkit toolkit, Composite parent) {
        this.toolkit = toolkit;

        composite = toolkit.createComposite(parent);
        GridLayoutFactory.fillDefaults().extendedMargins(0, 0, 0, 5).applyTo(composite);

        GerritChange change = GerritUtil.getChange(getTaskData());
        if (change != null) {
            for (PatchSetDetail patchSetDetail : change.getPatchSetDetails()) {
                PatchSet.Id patchSetId = patchSetDetail.getPatchSet().getId();
                PatchSetPublishDetail publishDetail = change.getPublishDetailByPatchSetId().get(patchSetId);
                createSubSection(change.getChangeDetail(), patchSetDetail, publishDetail, getSection());
            }
        }

        return composite;
    }

    protected void doAbandon(PatchSet patchSet) {
        AbandonDialog dialog = new AbandonDialog(getShell(), getTask(), patchSet);
        openOperationDialog(dialog);
    }

    protected void doPublish(PatchSetPublishDetail publishDetail) {
        TaskAttribute comment = getTaskData().getRoot().getAttribute(TaskAttribute.COMMENT_NEW);
        String editorCommentText = comment != null ? comment.getValue() : "";
        PublishDialog dialog = new PublishDialog(getShell(), getTask(), publishDetail, addedDrafts,
                editorCommentText);
        openOperationDialog(dialog);
        if (dialog.getReturnCode() == Window.OK && comment != null) {
            comment.clearValues();
            getTaskEditorPage().doSave(new NullProgressMonitor());
            try {
                TasksUi.getTaskDataManager().discardEdits(getTask());
            } catch (CoreException e) {
                Status status = new Status(IStatus.ERROR, GerritUiPlugin.PLUGIN_ID,
                        "Error while clearing task status.", e);
                TasksUiInternal.displayStatus("Clear outgoing task status failed", status);
            }
        }
    }

    protected void doFetch(ChangeDetail changeDetail, PatchSetDetail patchSetDetail) {
        GerritToGitMapping mapping = getRepository(changeDetail);
        if (mapping != null) {
            String refName = patchSetDetail.getPatchSet().getRefName();
            FetchGerritChangeWizard wizard = new FetchGerritChangeWizard(mapping.getRepository(), refName);
            WizardDialog wizardDialog = new WizardDialog(getShell(), wizard);
            wizardDialog.setHelpAvailable(false);
            wizardDialog.open();
        }
    }

    private GerritToGitMapping getRepository(ChangeDetail changeDetail) {
        GerritToGitMapping mapper = new GerritToGitMapping(getTaskEditorPage().getTaskRepository(), getConfig(),
                getGerritProject(changeDetail));
        try {
            if (mapper.find() != null) {
                return mapper;
            } else if (mapper.getGerritProject() != null) {
                boolean create = MessageDialog.openQuestion(getShell(), "Clone Git Repository",
                        "The referenced Git repository was not found in the workspace. Clone Git repository?");
                if (create) {
                    int response = EGitUiUtil.openCloneRepositoryWizard(getShell(),
                            getTaskEditorPage().getTaskRepository(), mapper.getGerritProject());
                    if (response == Window.OK && mapper.find() != null) {
                        return mapper;
                    }
                }
            } else {
                String message = NLS.bind("No Git repository found for fetching Gerrit change {0}",
                        getTask().getTaskKey());
                String reason = NLS.bind(
                        "No remote config found that has fetch URL with host ''{0}'' and path matching ''{1}''",
                        mapper.getGerritHost(), mapper.getGerritProjectName());
                GerritCorePlugin.logError(message, null);
                ErrorDialog.openError(getShell(), "Gerrit Fetch Change Error", message,
                        new Status(IStatus.ERROR, GerritUiPlugin.PLUGIN_ID, reason));
            }
        } catch (IOException e) {
            Status status = new Status(IStatus.ERROR, GerritUiPlugin.PLUGIN_ID, "Error accessing Git repository",
                    e);
            StatusManager.getManager().handle(status, StatusManager.BLOCK | StatusManager.SHOW | StatusManager.LOG);
        }
        return null;
    }

    protected void doCompareWithInSynchronizeView(ChangeDetail changeDetail, PatchSet base, PatchSet target) {
        GerritToGitMapping mapping = getRepository(changeDetail);
        if (mapping != null) {
            ComparePatchSetJob job = new ComparePatchSetJob(mapping.getRepository(), mapping.getRemote(), base,
                    target);
            job.schedule();
        }
    }

    protected void doCompareWith(ChangeDetail changeDetail, PatchSet base, PatchSet target) {
        OpenPatchSetJob job = new OpenPatchSetJob(getTaskEditorPage().getTaskRepository(), getTask(), changeDetail,
                base, target, cache);
        job.schedule();
    }

    private String getGerritProject(ChangeDetail changeDetail) {
        return changeDetail.getChange().getProject().get();
    }

    protected void doRebase(PatchSet patchSet) {
        RebaseDialog dialog = new RebaseDialog(getShell(), getTask(), patchSet);
        openOperationDialog(dialog);
    }

    protected void doRestore(PatchSet patchSet) {
        RestoreDialog dialog = new RestoreDialog(getShell(), getTask(), patchSet);
        openOperationDialog(dialog);
    }

    protected void doSubmit(PatchSet patchSet) {
        SubmitDialog dialog = new SubmitDialog(getShell(), getTask(), patchSet);
        openOperationDialog(dialog);
    }

    @Override
    protected boolean shouldExpandOnCreate() {
        return true;
    }

    void createSubSectionContents(final ChangeDetail changeDetail, final PatchSetDetail patchSetDetail,
            PatchSetPublishDetail publishDetail, Section subSection) {
        Composite composite = toolkit.createComposite(subSection);
        GridLayoutFactory.fillDefaults().numColumns(2).applyTo(composite);
        subSection.setClient(composite);

        Label authorLabel = new Label(composite, SWT.NONE);
        authorLabel.setForeground(toolkit.getColors().getColor(IFormColors.TITLE));
        authorLabel.setText("Author");

        Text authorText = new Text(composite, SWT.READ_ONLY);
        authorText.setText(GerritUtil.getUserLabel(patchSetDetail.getInfo().getAuthor()));

        Label committerLabel = new Label(composite, SWT.NONE);
        committerLabel.setForeground(toolkit.getColors().getColor(IFormColors.TITLE));
        committerLabel.setText("Committer");

        Text committerText = new Text(composite, SWT.READ_ONLY);
        committerText.setText(GerritUtil.getUserLabel(patchSetDetail.getInfo().getCommitter()));

        Label commitLabel = new Label(composite, SWT.NONE);
        commitLabel.setForeground(toolkit.getColors().getColor(IFormColors.TITLE));
        commitLabel.setText("Commit");

        Hyperlink commitLink = new Hyperlink(composite, SWT.READ_ONLY);
        commitLink.setText(patchSetDetail.getPatchSet().getRevision().get());
        commitLink.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent event) {
                GerritToGitMapping mapping = getRepository(changeDetail);
                if (mapping != null) {
                    final FetchPatchSetJob job = new FetchPatchSetJob("Opening Commit Viewer",
                            mapping.getRepository(), mapping.getRemote(), patchSetDetail.getPatchSet());
                    job.schedule();
                    job.addJobChangeListener(new JobChangeAdapter() {
                        @Override
                        public void done(IJobChangeEvent event) {
                            Display.getDefault().asyncExec(new Runnable() {
                                @Override
                                public void run() {
                                    CommitEditor.openQuiet(job.getCommit());
                                }
                            });
                        }
                    });
                }
            }
        });

        Label refLabel = new Label(composite, SWT.NONE);
        refLabel.setForeground(toolkit.getColors().getColor(IFormColors.TITLE));
        refLabel.setText("Ref");

        Text refText = new Text(composite, SWT.READ_ONLY);
        refText.setText(patchSetDetail.getPatchSet().getRefName());

        final TableViewer viewer = new TableViewer(composite, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL);
        GridDataFactory.fillDefaults().span(2, 1).grab(true, true).hint(500, SWT.DEFAULT)
                .applyTo(viewer.getControl());
        viewer.setContentProvider(new IStructuredContentProvider() {
            private EContentAdapter modelAdapter;

            public void dispose() {
                // ignore
            }

            public Object[] getElements(Object inputElement) {
                return getReviewItems(inputElement).toArray();
            }

            private List<IReviewItem> getReviewItems(Object inputElement) {
                if (inputElement instanceof IReviewItemSet) {
                    return ((IReviewItemSet) inputElement).getItems();
                }
                return Collections.emptyList();
            }

            public void inputChanged(final Viewer viewer, Object oldInput, Object newInput) {
                if (modelAdapter != null) {
                    for (IReviewItem item : getReviewItems(oldInput)) {
                        ((EObject) item).eAdapters().remove(modelAdapter);
                    }
                    addedDrafts = 0;
                }

                if (newInput instanceof IReviewItemSet) {
                    // monitors any new topics that are added
                    modelAdapter = new EContentAdapter() {
                        @Override
                        public void notifyChanged(Notification notification) {
                            super.notifyChanged(notification);
                            if (notification.getFeatureID(IReviewItem.class) == ReviewsPackage.REVIEW_ITEM__TOPICS
                                    && notification.getEventType() == Notification.ADD) {
                                viewer.refresh();
                                addedDrafts++;
                            }
                        }
                    };
                    for (Object item : getReviewItems(newInput)) {
                        ((EObject) item).eAdapters().add(modelAdapter);
                    }
                }
            }
        });
        viewer.setLabelProvider(new DelegatingStyledCellLabelProvider(new ReviewItemLabelProvider()));
        viewer.addOpenListener(new IOpenListener() {
            public void open(OpenEvent event) {
                IStructuredSelection selection = (IStructuredSelection) event.getSelection();
                IFileItem item = (IFileItem) selection.getFirstElement();
                if (item != null) {
                    doOpen((IReviewItemSet) viewer.getInput(), item);
                }
            }
        });

        IReviewItemSet itemSet = GerritUtil.createInput(changeDetail, new GerritPatchSetContent(patchSetDetail),
                cache);
        viewer.setInput(itemSet);

        Composite actionComposite = createActions(changeDetail, patchSetDetail, publishDetail, composite);
        GridDataFactory.fillDefaults().span(2, 1).applyTo(actionComposite);

        subSectionExpanded(changeDetail, patchSetDetail, subSection, viewer);
        EditorUtil.addScrollListener(viewer.getTable());

        getTaskEditorPage().reflow();
    }

    private void doOpen(IReviewItemSet items, IFileItem item) {
        if (item.getBase() == null || item.getTarget() == null) {
            getTaskEditorPage().getEditor().setMessage("The selected file is not available, yet",
                    IMessageProvider.WARNING);
            return;
        }

        GerritReviewBehavior behavior = new GerritReviewBehavior(getTask());
        CompareConfiguration configuration = new CompareConfiguration();
        CompareUI.openCompareEditor(new FileItemCompareEditorInput(configuration, item, behavior));
    }

}