com.atlassian.connector.eclipse.internal.crucible.ui.wizards.SelectScmChangesetsPage.java Source code

Java tutorial

Introduction

Here is the source code for com.atlassian.connector.eclipse.internal.crucible.ui.wizards.SelectScmChangesetsPage.java

Source

/*******************************************************************************
 * Copyright (c) 2009 Atlassian 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:
 *     Atlassian - initial API and implementation
 ******************************************************************************/

package com.atlassian.connector.eclipse.internal.crucible.ui.wizards;

import com.atlassian.connector.eclipse.internal.crucible.core.TaskRepositoryUtil;
import com.atlassian.connector.eclipse.internal.crucible.ui.CrucibleImages;
import com.atlassian.connector.eclipse.internal.crucible.ui.CrucibleUiPlugin;
import com.atlassian.connector.eclipse.internal.crucible.ui.CrucibleUiUtil;
import com.atlassian.connector.eclipse.team.ui.ICustomChangesetLogEntry;
import com.atlassian.connector.eclipse.team.ui.ITeamUiResourceConnector;
import com.atlassian.connector.eclipse.team.ui.ITeamUiResourceConnector2;
import com.atlassian.connector.eclipse.team.ui.ScmRepository;
import com.atlassian.connector.eclipse.team.ui.TeamUiUtils;
import com.atlassian.theplugin.commons.util.MiscUtil;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.provisional.commons.ui.CommonImages;
import org.eclipse.mylyn.tasks.core.TaskRepository;
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.Image;
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.Tree;
import org.eclipse.ui.views.navigator.ResourceComparator;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Page for selecting changeset for the new review
 * 
 * @author Thomas Ehrnhoefer
 */
public class SelectScmChangesetsPage extends AbstractCrucibleWizardPage {

    private static final int LIMIT = 25;

    private static final String EMPTY_NODE = "No changesets available.";

    private class ChangesetLabelProvider extends LabelProvider {
        @Override
        public Image getImage(Object element) {
            if (element == null) {
                return null;
            }
            if (element instanceof ScmRepository) {
                //return FishEyeImages.getImage(FishEyeImages.REPOSITORY);
            } else if (element instanceof ICustomChangesetLogEntry) {
                return CommonImages.getImage(CrucibleImages.CHANGESET);
            } else if (element == EMPTY_NODE) {
                return null;
            } else if (element instanceof String) {
                return CommonImages.getImage(CrucibleImages.FILE);
            }
            return null;
        }

        @Override
        public String getText(Object element) {
            if (element == null) {
                return "";
            }
            if (element instanceof ScmRepository) {
                return ((ScmRepository) element).getScmPath();
            } else if (element instanceof ICustomChangesetLogEntry) {
                ICustomChangesetLogEntry logEntry = (ICustomChangesetLogEntry) element;
                return logEntry.getRevision() + " [" + logEntry.getAuthor() + "] - " + logEntry.getComment();
            } else if (element == EMPTY_NODE) {
                return EMPTY_NODE;
            } else if (element instanceof String) {
                return (String) element;
            }
            return "";
        }
    }

    private class ChangesetContentProvider implements ITreeContentProvider {

        private Map<ScmRepository, SortedSet<ICustomChangesetLogEntry>> logEntries;

        public Object[] getChildren(Object parentElement) {
            if (logEntries == null || parentElement == null) {
                return new Object[0];
            }
            if (parentElement instanceof ScmRepository) {
                //root, repository URLs
                if (logEntries.get(parentElement) == null || logEntries.get(parentElement).size() == 0) {
                    //if no retrieved changeset, create fake node for lazy loading
                    return new String[] { EMPTY_NODE };
                }
                return logEntries.get(parentElement).toArray();
            } else if (parentElement instanceof ICustomChangesetLogEntry) {
                //changeset files
                return ((ICustomChangesetLogEntry) parentElement).getChangedFiles();
            }
            return new Object[0];
        }

        @SuppressWarnings("rawtypes")
        public Object getParent(Object element) {
            if (logEntries == null) {
                return null;
            }
            if (element instanceof Map || element instanceof ScmRepository) {
                //root, repository URLs
                return null;
            } else if (element instanceof ICustomChangesetLogEntry) {
                //changeset elements
                return ((ICustomChangesetLogEntry) element).getRepository();
            }
            return null;
        }

        @SuppressWarnings("rawtypes")
        public boolean hasChildren(Object element) {
            if (logEntries == null) {
                return false;
            }
            if (element instanceof Map) {
                //root, repository URLs
                return logEntries.size() > 0;
            } else if (element instanceof ScmRepository) {
                //change sets for a repository
                //            return logEntries.get(element).size() > 0;
                return true;
            } else if (element instanceof ICustomChangesetLogEntry) {
                //changeset elements
                return ((ICustomChangesetLogEntry) element).getChangedFiles().length > 0;
            }
            return false;
        }

        /**
         * @return array of map keys (Repository URLs)
         */
        public Object[] getElements(Object inputElement) {
            if (logEntries == null) {
                return new Object[0];
            }
            //repositories 
            return logEntries.keySet().toArray();
        }

        public void dispose() {
            // ignore

        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            if (newInput instanceof Map) {
                logEntries = (Map<ScmRepository, SortedSet<ICustomChangesetLogEntry>>) newInput;
            }
        }

    }

    private final Map<ScmRepository, SortedSet<ICustomChangesetLogEntry>> availableLogEntries;

    private final Map<ScmRepository, SortedSet<ICustomChangesetLogEntry>> selectedLogEntries;

    private TreeViewer availableTreeViewer;

    private TreeViewer selectedTreeViewer;

    private Button addButton;

    private Button removeButton;

    private MenuItem removeChangesetMenuItem;

    private MenuItem getNextRevisionsMenuItem;

    private MenuItem addChangesetMenuItem;

    private final TaskRepository taskRepository;

    private DefineRepositoryMappingButton mappingButton;

    public SelectScmChangesetsPage(TaskRepository repository) {
        this(repository, new TreeSet<ICustomChangesetLogEntry>());
    }

    public SelectScmChangesetsPage(TaskRepository repository, SortedSet<ICustomChangesetLogEntry> logEntries) {
        super("crucibleChangesets"); //$NON-NLS-1$
        setTitle("Select Changesets");
        setDescription("Select the changesets that should be included in the review.");

        this.taskRepository = repository;
        this.availableLogEntries = new HashMap<ScmRepository, SortedSet<ICustomChangesetLogEntry>>();
        this.selectedLogEntries = new HashMap<ScmRepository, SortedSet<ICustomChangesetLogEntry>>();

        if (logEntries != null && logEntries.size() > 0) {
            this.selectedLogEntries.put(logEntries.first().getRepository(), logEntries);
        }
    }

    public void createControl(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayout(GridLayoutFactory.fillDefaults().numColumns(3).margins(5, 5).create());

        Label label = new Label(composite, SWT.NONE);
        label.setText("Select changesets from your repositories:");
        GridDataFactory.fillDefaults().span(2, 1).applyTo(label);

        new Label(composite, SWT.NONE).setText("Changesets selected for the review:");

        createLeftViewer(composite);

        createButtonComp(composite);

        createRightViewer(composite);

        mappingButton = new DefineRepositoryMappingButton(this, composite, getTaskRepository());

        Control button = mappingButton.getControl();
        GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(button);

        Dialog.applyDialogFont(composite);
        setControl(composite);
    }

    /*
     * checks if page is complete updates the buttons
     */
    public void validatePage() {
        setErrorMessage(null);

        // Check if all custom repositories are mapped to Crucible source repositories
        boolean allFine = true;
        for (Set<ICustomChangesetLogEntry> entries : selectedLogEntries.values()) {
            for (ICustomChangesetLogEntry entry : entries) {
                String[] files = entry.getChangedFiles();
                if (files == null || files.length == 0) {
                    continue;
                }
                for (String file : files) {
                    String scmPath = entry.getRepository().getRootPath() + '/' + file;
                    Map.Entry<String, String> sourceRepository = TaskRepositoryUtil.getMatchingSourceRepository(
                            TaskRepositoryUtil.getScmRepositoryMappings(getTaskRepository()), scmPath);

                    if (sourceRepository == null) {
                        mappingButton.setMissingMapping(entry.getRepository().getScmPath());
                        setErrorMessage(
                                NLS.bind("SCM repository path {0} is not mapped to Crucible repository.", scmPath));
                        allFine = false;
                        break;
                    }
                }
                if (!allFine) {
                    break;
                }
            }
            if (!allFine) {
                break;
            }
        }
        setPageComplete(allFine);
        getContainer().updateButtons();
    }

    private void createLeftViewer(Composite parent) {
        Tree tree = new Tree(parent, SWT.MULTI | SWT.BORDER);
        availableTreeViewer = new TreeViewer(tree);

        GridDataFactory.fillDefaults().grab(true, true).hint(300, 220).applyTo(tree);
        availableTreeViewer.setLabelProvider(new ChangesetLabelProvider());
        availableTreeViewer.setContentProvider(new ChangesetContentProvider());
        availableTreeViewer.setComparator(new ResourceComparator(ResourceComparator.NAME));
        availableTreeViewer.setComparator(new ViewerComparator() {
            @Override
            public int compare(Viewer viewer, Object e1, Object e2) {
                if (e1 instanceof ICustomChangesetLogEntry && e2 instanceof ICustomChangesetLogEntry) {
                    return ((ICustomChangesetLogEntry) e2).getDate()
                            .compareTo(((ICustomChangesetLogEntry) e1).getDate());
                }
                return super.compare(viewer, e1, e2);
            }
        });
        availableTreeViewer.setInput(ResourcesPlugin.getWorkspace().getRoot());
        final Menu contextMenuSource = new Menu(getShell(), SWT.POP_UP);
        tree.setMenu(contextMenuSource);
        addChangesetMenuItem = new MenuItem(contextMenuSource, SWT.PUSH);
        addChangesetMenuItem.setText("Add to Review");

        new MenuItem(contextMenuSource, SWT.SEPARATOR);

        addChangesetMenuItem.setEnabled(false);
        getNextRevisionsMenuItem = new MenuItem(contextMenuSource, SWT.PUSH);
        getNextRevisionsMenuItem.setText(String.format("Get %d More Revisions", LIMIT));
        getNextRevisionsMenuItem.setEnabled(false);
        getNextRevisionsMenuItem.addSelectionListener(new SelectionAdapter() {
            @SuppressWarnings("unchecked")
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeSelection selection = getTreeSelection(availableTreeViewer);
                if (selection != null) {
                    Iterator<Object> iterator = (selection).iterator();
                    Set<ScmRepository> alreadyDone = new TreeSet<ScmRepository>();
                    while (iterator.hasNext()) {
                        Object element = iterator.next();
                        ScmRepository repository = null;
                        if (element instanceof ICustomChangesetLogEntry) {
                            repository = ((ICustomChangesetLogEntry) element).getRepository();
                        } else if (element instanceof ScmRepository) {
                            repository = (ScmRepository) element;
                        }
                        if (repository != null && !alreadyDone.contains(repository)) {
                            SortedSet<ICustomChangesetLogEntry> logEntries = availableLogEntries.get(repository);
                            int currentNumberOfEntries = logEntries == null ? 0 : logEntries.size();
                            updateChangesets(repository, currentNumberOfEntries + LIMIT);
                            alreadyDone.add(repository);
                        }
                    }
                } else {
                    //update all
                    for (ScmRepository repository : availableLogEntries.keySet()) {
                        SortedSet<ICustomChangesetLogEntry> logEntries = availableLogEntries.get(repository);
                        int currentNumberOfEntries = logEntries == null ? 0 : logEntries.size();
                        updateChangesets(repository, currentNumberOfEntries + LIMIT);
                    }
                }
            }
        });
        addChangesetMenuItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                addChangesets();
                updateButtonEnablement();
            }
        });
        tree.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                updateButtonEnablement();
            }
        });
        availableTreeViewer.addTreeListener(new ITreeViewerListener() {
            public void treeCollapsed(TreeExpansionEvent event) {
                // ignore
            }

            public void treeExpanded(TreeExpansionEvent event) {
                // first time of expanding: retrieve first 10 changesets
                final Object object = event.getElement();
                if (object instanceof ScmRepository) {
                    refreshRepository((ScmRepository) object);
                }

            }
        });
        availableTreeViewer.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                IStructuredSelection selection = (IStructuredSelection) event.getSelection();
                Object object = selection.getFirstElement();
                if (availableTreeViewer.isExpandable(object)) {
                    if (!availableTreeViewer.getExpandedState(object) && object instanceof ScmRepository) {
                        refreshRepository((ScmRepository) object);
                        return;
                    }
                    availableTreeViewer.setExpandedState(object, !availableTreeViewer.getExpandedState(object));
                }
            }
        });
    }

    private void refreshRepository(final ScmRepository object) {
        SortedSet<ICustomChangesetLogEntry> logEntries = availableLogEntries.get(object);
        if (logEntries == null) {
            updateChangesets(object, LIMIT);
        }
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                // expand tree after filling
                availableTreeViewer.expandToLevel(object, 1);
            }
        });
    }

    private void createButtonComp(Composite composite) {
        Composite buttonComp = new Composite(composite, SWT.NONE);
        buttonComp.setLayout(GridLayoutFactory.fillDefaults().numColumns(1).create());
        GridDataFactory.fillDefaults().grab(false, true).applyTo(buttonComp);

        addButton = new Button(buttonComp, SWT.PUSH);
        addButton.setText("Add -->");
        addButton.setToolTipText("Add all selected changesets");
        addButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                addChangesets();
                updateButtonEnablement();
            }
        });
        addButton.setEnabled(false);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(addButton);

        removeButton = new Button(buttonComp, SWT.PUSH);
        removeButton.setText("<-- Remove");
        removeButton.setToolTipText("Remove all selected changesets");
        removeButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                removeChangesets();
                updateButtonEnablement();
            }

        });
        removeButton.setEnabled(false);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(removeButton);
    }

    private void createRightViewer(Composite composite) {
        Tree tree = new Tree(composite, SWT.MULTI | SWT.BORDER);
        selectedTreeViewer = new TreeViewer(tree);

        GridDataFactory.fillDefaults().grab(true, true).hint(300, 220).applyTo(tree);
        //GridDataFactory.fillDefaults().grab(true, true).hint(300, SWT.DEFAULT).applyTo(tree);
        selectedTreeViewer.setLabelProvider(new ChangesetLabelProvider());
        selectedTreeViewer.setContentProvider(new ChangesetContentProvider());
        selectedTreeViewer.setComparator(new ResourceComparator(ResourceComparator.NAME));
        final Menu contextMenuSource = new Menu(getShell(), SWT.POP_UP);
        tree.setMenu(contextMenuSource);
        removeChangesetMenuItem = new MenuItem(contextMenuSource, SWT.PUSH);
        removeChangesetMenuItem.setText("Remove from Review");
        removeChangesetMenuItem.setEnabled(false);
        removeChangesetMenuItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                removeChangesets();
                updateButtonEnablement();
            }
        });

        tree.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                updateButtonEnablement();
            }
        });
    }

    private void updateButtonEnablement() {
        //right viewer
        TreeSelection selection = validateTreeSelection(selectedTreeViewer, false);
        removeButton.setEnabled(selection != null && !selection.isEmpty());
        removeChangesetMenuItem.setEnabled(selection != null && !selection.isEmpty());
        //left viewer
        selection = validateTreeSelection(availableTreeViewer, true);
        boolean changesetsOnly = hasChangesetsOnly(selection);
        addButton.setEnabled(selection != null && !selection.isEmpty()
                && !hasAlreadyChosenChangesetSelected(selection) && changesetsOnly);
        addChangesetMenuItem.setEnabled(selection != null && !selection.isEmpty()
                && !hasAlreadyChosenChangesetSelected(selection) && changesetsOnly);
        getNextRevisionsMenuItem.setEnabled(selection != null && !selection.isEmpty());
    }

    @SuppressWarnings("unchecked")
    private boolean hasChangesetsOnly(TreeSelection selection) {
        if (selection != null) {
            Iterator<Object> iterator = (selection).iterator();
            while (iterator.hasNext()) {
                Object element = iterator.next();
                if (element instanceof ScmRepository) {
                    return false;
                }
            }
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    private boolean hasAlreadyChosenChangesetSelected(TreeSelection selection) {
        for (ScmRepository repository : selectedLogEntries.keySet()) {
            Iterator<Object> iterator = (selection).iterator();
            while (iterator.hasNext()) {
                Object element = iterator.next();
                if (element instanceof ICustomChangesetLogEntry) {
                    if (selectedLogEntries.get(repository).contains(element)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private void updateRepositories() {
        final MultiStatus status = new MultiStatus(CrucibleUiPlugin.PLUGIN_ID, IStatus.WARNING,
                "Error while retrieving repositories", null);

        IRunnableWithProgress getRepositories = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                Collection<ScmRepository> repositories = TeamUiUtils.getRepositories(monitor);
                if (repositories != null) {
                    for (ScmRepository repository : repositories) {
                        availableLogEntries.put(repository, null);
                    }
                }
            }
        };

        try {
            setErrorMessage(null);
            getContainer().run(true, true, getRepositories); // blocking operation
        } catch (Exception e) {
            status.add(
                    new Status(IStatus.WARNING, CrucibleUiPlugin.PLUGIN_ID, "Failed to retrieve repositories", e));
        }

        if (availableLogEntries != null) {
            availableTreeViewer.setInput(availableLogEntries);
        }

        if (status.getChildren().length > 0 && status.getSeverity() == IStatus.ERROR) { //only log errors, swallow warnings
            setErrorMessage("Error while retrieving repositories. See Error Log for details.");
            StatusHandler.log(status);
        }
    }

    private void updateChangesets(final ScmRepository repository, final int numberToRetrieve) {
        final IStatus[] status = { Status.OK_STATUS };

        IRunnableWithProgress getChangesets = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                try {
                    ITeamUiResourceConnector tc = repository.getTeamResourceConnector();
                    if (tc instanceof ITeamUiResourceConnector2) {
                        SortedSet<ICustomChangesetLogEntry> retrieved = ((ITeamUiResourceConnector2) tc)
                                .getLatestChangesets(repository.getScmPath(), numberToRetrieve, monitor);

                        if (availableLogEntries.containsKey(repository)
                                && availableLogEntries.get(repository) != null) {
                            availableLogEntries.get(repository).addAll(retrieved);
                        } else {
                            availableLogEntries.put(repository, retrieved);
                        }
                    } else {
                        status[0] = new Status(IStatus.ERROR, CrucibleUiPlugin.PLUGIN_ID,
                                "This repository is managed by SCM integration that's compatible only with Crucible 2.x");
                    }
                } catch (CoreException e) {
                    status[0] = e.getStatus();
                }
            }
        };

        try {
            setErrorMessage(null);
            getContainer().run(false, true, getChangesets); // blocking operation
        } catch (Exception e) {
            status[0] = new Status(IStatus.WARNING, CrucibleUiPlugin.PLUGIN_ID, "Failed to retrieve changesets", e);
        }

        if (availableLogEntries != null && availableLogEntries.get(repository) != null) {
            availableTreeViewer.setInput(availableLogEntries);
        }

        if (status[0].getSeverity() == IStatus.ERROR) { //only log errors, swallow warnings
            setErrorMessage(String.format("Error while retrieving changesets (%s). See Error Log for details.",
                    status[0].getMessage()));
            StatusHandler.log(status[0]);
        }
    }

    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);
        if (visible && (availableLogEntries.isEmpty() || !CrucibleUiUtil.hasCachedData(getTaskRepository()))) {
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    if (availableLogEntries.isEmpty()) {
                        updateRepositories();
                        selectedTreeViewer.setInput(selectedLogEntries);
                        validatePage();
                    }
                }
            });
        }
    }

    private TaskRepository getTaskRepository() {
        return taskRepository;
    }

    private void addChangesets() {
        TreeSelection selection = getTreeSelection(availableTreeViewer);
        addOrRemoveChangesets(selection, true);
    }

    private void removeChangesets() {
        TreeSelection selection = getTreeSelection(selectedTreeViewer);
        addOrRemoveChangesets(selection, false);
    }

    @SuppressWarnings("unchecked")
    private void addOrRemoveChangesets(TreeSelection selection, boolean add) {
        if (selection != null) {
            Iterator<Object> iterator = (selection).iterator();
            Set<ScmRepository> expandedRepositories = new HashSet<ScmRepository>();
            while (iterator.hasNext()) {
                Object element = iterator.next();
                if (element instanceof ICustomChangesetLogEntry) {
                    ScmRepository repository = ((ICustomChangesetLogEntry) element).getRepository();
                    SortedSet<ICustomChangesetLogEntry> changesets = selectedLogEntries.get(repository);
                    if (changesets == null) {
                        changesets = new TreeSet<ICustomChangesetLogEntry>();
                    }
                    if (add) {
                        changesets.add((ICustomChangesetLogEntry) element);
                    } else {
                        changesets.remove(element);
                    }
                    if (changesets.size() > 0) {
                        selectedLogEntries.put(repository, changesets);
                    } else {
                        selectedLogEntries.remove(repository);
                    }
                    expandedRepositories.add(repository);
                }
            }
            selectedTreeViewer.setInput(selectedLogEntries);
            selectedTreeViewer.setExpandedElements(expandedRepositories.toArray());
        }
        validatePage();
    }

    @SuppressWarnings("unchecked")
    private TreeSelection validateTreeSelection(TreeViewer treeViewer, boolean allowChangesetsSelection) {
        TreeSelection selection = getTreeSelection(treeViewer);
        if (selection != null) {
            ArrayList<TreePath> validSelections = new ArrayList<TreePath>();
            Iterator<Object> iterator = (selection).iterator();
            while (iterator.hasNext()) {
                Object element = iterator.next();
                if (element instanceof ICustomChangesetLogEntry) {
                    validSelections.add((selection).getPathsFor(element)[0]);
                } else if (allowChangesetsSelection && element instanceof ScmRepository) {
                    validSelections.add((selection).getPathsFor(element)[0]);
                }
            }
            //set new selection
            TreeSelection newSelection = new TreeSelection(
                    validSelections.toArray(new TreePath[validSelections.size()]),
                    (selection).getElementComparer());
            treeViewer.setSelection(newSelection);
        } else {
            treeViewer.setSelection(new TreeSelection());
        }
        return getTreeSelection(treeViewer);
    }

    private TreeSelection getTreeSelection(TreeViewer treeViewer) {
        ISelection selection = treeViewer.getSelection();
        if (selection instanceof TreeSelection) {
            return (TreeSelection) selection;
        }
        return null;
    }

    public Map<String, Set<String>> getSelectedChangesets() {
        Map<String, Set<String>> result = MiscUtil.buildHashMap();

        for (SortedSet<ICustomChangesetLogEntry> entries : selectedLogEntries.values()) {
            for (ICustomChangesetLogEntry entry : entries) {
                String[] files = entry.getChangedFiles();
                if (files == null || files.length == 0) {
                    continue;
                }
                for (String file : files) {
                    Map.Entry<String, String> sourceRepository = TaskRepositoryUtil.getMatchingSourceRepository(
                            TaskRepositoryUtil.getScmRepositoryMappings(getTaskRepository()),
                            entry.getRepository().getRootPath() + '/' + file);
                    if (sourceRepository != null) {
                        if (!result.containsKey(sourceRepository.getValue())) {
                            result.put(sourceRepository.getValue(), new HashSet<String>());
                        }
                        result.get(sourceRepository.getValue()).add(entry.getRevision());
                    }
                }
            }
        }
        return result;
    }

}