com.collabnet.subversion.merge.wizards.MergeWizardUnblockRevisionsPage.java Source code

Java tutorial

Introduction

Here is the source code for com.collabnet.subversion.merge.wizards.MergeWizardUnblockRevisionsPage.java

Source

/*******************************************************************************
 * Copyright (c) 2009 CollabNet.
 * 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:
 *     CollabNet - initial API and implementation
 ******************************************************************************/
package com.collabnet.subversion.merge.wizards;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.CompareViewerSwitchingPane;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.team.core.TeamException;
import org.tigris.subversion.subclipse.core.ISVNLocalResource;
import org.tigris.subversion.subclipse.core.ISVNRemoteResource;
import org.tigris.subversion.subclipse.core.ISVNRepositoryLocation;
import org.tigris.subversion.subclipse.core.commands.GetLogsCommand;
import org.tigris.subversion.subclipse.core.history.AliasManager;
import org.tigris.subversion.subclipse.core.history.ILogEntry;
import org.tigris.subversion.subclipse.core.history.LogEntry;
import org.tigris.subversion.subclipse.core.history.LogEntryChangePath;
import org.tigris.subversion.subclipse.core.resources.RemoteFile;
import org.tigris.subversion.subclipse.core.resources.SVNWorkspaceRoot;
import org.tigris.subversion.subclipse.ui.ISVNUIConstants;
import org.tigris.subversion.subclipse.ui.SVNUIPlugin;
import org.tigris.subversion.subclipse.ui.compare.ResourceEditionNode;
import org.tigris.subversion.subclipse.ui.compare.SVNCompareEditorInput;
import org.tigris.subversion.subclipse.ui.history.ChangePathsTreeViewer;
import org.tigris.subversion.subclipse.ui.history.HistoryFolder;
import org.tigris.subversion.subclipse.ui.history.HistoryTableProvider;
import org.tigris.subversion.svnclientadapter.ISVNMergeInfo;
import org.tigris.subversion.svnclientadapter.SVNRevision;
import org.tigris.subversion.svnclientadapter.SVNRevisionRange;
import org.tigris.subversion.svnclientadapter.SVNUrl;

import com.collabnet.subversion.merge.Activator;
import com.collabnet.subversion.merge.Messages;

public class MergeWizardUnblockRevisionsPage extends WizardPage {
    private MergeWizardStandardPage standardPage;
    private String fromUrl;
    private SVNRevisionRange[] revisionRanges;
    private ArrayList entryArray;

    private IDialogSettings settings;
    private SashForm horizontalSash;
    private SashForm verticalSash;
    private HistoryTableProvider historyTableProvider;
    private ChangePathsTreeViewer changePathsViewer;
    private TableViewer tableHistoryViewer;
    private TextViewer textViewer;
    private Button showCompareButton;
    private CompareViewerSwitchingPane compareViewerPane;
    private ILogEntry[] entries;
    private IResource resource;
    private ISVNLocalResource svnResource;
    private ISVNRepositoryLocation repositoryLocation;
    private AliasManager tagManager;
    private ISVNRemoteResource remoteResource;
    private SVNRevision.Number[] allRevisions;
    private ILogEntry[] rangeEntries;
    private boolean pageShown;

    private SVNCompareEditorInput compareInput;
    private boolean showCompare;
    private Map<String, SVNCompareEditorInput> compareInputMap = new HashMap<String, SVNCompareEditorInput>();

    public MergeWizardUnblockRevisionsPage(String pageName, String title, ImageDescriptor titleImage,
            MergeWizardStandardPage standardPage) {
        super(pageName, title, titleImage);
        this.standardPage = standardPage;
        settings = Activator.getDefault().getDialogSettings();
    }

    public void createControl(Composite parent) {
        final MergeWizard wizard = (MergeWizard) getWizard();
        resource = wizard.getResource();
        if (resource != null) {
            svnResource = SVNWorkspaceRoot.getSVNResourceFor(resource);
            try {
                repositoryLocation = svnResource.getRepository();
            } catch (Exception e1) {
            }
        }

        Composite composite = new Composite(parent, SWT.NULL);
        GridLayout layout = new GridLayout();
        layout.verticalSpacing = 0;
        layout.marginHeight = 0;
        composite.setLayout(layout);
        GridData data = new GridData(GridData.FILL_BOTH);
        composite.setLayoutData(data);

        horizontalSash = new SashForm(composite, SWT.HORIZONTAL);
        horizontalSash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        verticalSash = new SashForm(horizontalSash, SWT.VERTICAL);
        GridLayout sashLayout = new GridLayout();
        sashLayout.verticalSpacing = 0;
        sashLayout.marginHeight = 0;
        verticalSash.setLayout(sashLayout);
        verticalSash.setLayoutData(new GridData(GridData.FILL_BOTH));

        Composite historyGroup = new Composite(verticalSash, SWT.NULL);
        GridLayout historyLayout = new GridLayout();
        historyLayout.verticalSpacing = 0;
        historyLayout.marginHeight = 0;
        historyGroup.setLayout(historyLayout);
        historyGroup.setLayoutData(new GridData(GridData.FILL_BOTH));

        historyTableProvider = new HistoryTableProvider(
                SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION | SWT.CHECK,
                "MergeWizardUnblockRevisionsPage"); //$NON-NLS-1$
        historyTableProvider.setIncludeMergeRevisions(false);
        historyTableProvider.setIncludeTags(false);
        tableHistoryViewer = historyTableProvider.createTable(historyGroup);
        data = new GridData(GridData.FILL_BOTH);
        data.widthHint = 500;
        data.heightHint = 100;
        tableHistoryViewer.getTable().setLayoutData(data);

        tableHistoryViewer.setContentProvider(new IStructuredContentProvider() {
            public void dispose() {
            }

            public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            }

            public Object[] getElements(Object inputElement) {
                if (entries == null)
                    return new ILogEntry[0];
                return entries;
            }
        });

        tableHistoryViewer.setInput(fromUrl);
        tableHistoryViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                setPageComplete(canFinish());
                ISelection selection = event.getSelection();
                if (selection == null || !(selection instanceof IStructuredSelection)) {
                    textViewer.setDocument(new Document("")); //$NON-NLS-1$                    
                    changePathsViewer.setInput(null);
                    return;
                }
                IStructuredSelection ss = (IStructuredSelection) selection;
                if (ss.size() != 1) {
                    textViewer.setDocument(new Document("")); //$NON-NLS-1$                    
                    changePathsViewer.setInput(null);
                    return;
                }
                LogEntry entry = (LogEntry) ss.getFirstElement();
                textViewer.setDocument(new Document(entry.getComment()));
                changePathsViewer.setCurrentLogEntry(entry);
                changePathsViewer.setInput(entry);
            }
        });

        MenuManager menuMgr = new MenuManager();
        Menu menu = menuMgr.createContextMenu(tableHistoryViewer.getTable());
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager menuMgr) {
                if (!tableHistoryViewer.getSelection().isEmpty()) {
                    menuMgr.add(new ToggleSelectionAction());
                }
            }
        });
        menuMgr.setRemoveAllWhenShown(true);
        tableHistoryViewer.getTable().setMenu(menu);

        Composite commentGroup = new Composite(verticalSash, SWT.NULL);
        GridLayout commentLayout = new GridLayout();
        commentLayout.verticalSpacing = 0;
        commentLayout.marginHeight = 0;
        commentGroup.setLayout(commentLayout);
        commentGroup.setLayoutData(new GridData(GridData.FILL_BOTH));

        textViewer = new TextViewer(commentGroup,
                SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.READ_ONLY);
        data = new GridData(GridData.FILL_BOTH);
        data.heightHint = 100;
        data.widthHint = 500;
        textViewer.getControl().setLayoutData(data);

        Composite pathGroup = new Composite(verticalSash, SWT.NULL);
        GridLayout pathLayout = new GridLayout();
        pathLayout.verticalSpacing = 0;
        pathLayout.marginHeight = 0;
        pathGroup.setLayout(pathLayout);
        pathGroup.setLayoutData(new GridData(GridData.FILL_BOTH));

        ViewForm viewerPane = new ViewForm(pathGroup, SWT.BORDER | SWT.FLAT);
        viewerPane.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        CLabel toolbarLabel = new CLabel(viewerPane, SWT.NONE) {
            public Point computeSize(int wHint, int hHint, boolean changed) {
                return super.computeSize(wHint, Math.max(24, hHint), changed);
            }
        };
        toolbarLabel.setText(Messages.MergeWizardRevisionsPage_2);
        viewerPane.setTopLeft(toolbarLabel);
        ToolBar toolbar = new ToolBar(viewerPane, SWT.FLAT);
        viewerPane.setTopCenter(toolbar);
        ToolBarManager toolbarManager = new ToolBarManager(toolbar);

        toolbarManager.add(new Separator());
        toolbarManager.add(new ControlContribution("showCompare") { //$NON-NLS-1$
            @Override
            protected Control createControl(Composite parent) {
                showCompareButton = new Button(parent, SWT.TOGGLE | SWT.FLAT);
                showCompareButton.setImage(SVNUIPlugin.getImage(ISVNUIConstants.IMG_SYNCPANE));
                showCompareButton.setToolTipText(Messages.MergeWizardRevisionsPage_4);
                showCompareButton.setSelection(showCompare);
                showCompareButton.addSelectionListener(new SelectionAdapter() {
                    public void widgetSelected(SelectionEvent e) {
                        showComparePane(!showCompare);
                        if (showCompare) {
                            compareRevisions();
                        }
                    }
                });
                return showCompareButton;
            }
        });

        toolbarManager.update(true);

        ChangePathsTreeContentProvider contentProvider = new ChangePathsTreeContentProvider();
        changePathsViewer = new ChangePathsTreeViewer(viewerPane, contentProvider);

        viewerPane.setContent(changePathsViewer.getTree());

        changePathsViewer.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                compareRevisions();
            }
        });

        changePathsViewer.getTree().addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                if (showCompare) {
                    compareRevisions();
                }
            }
        });

        setPageComplete(false);

        setMessage(Messages.MergeWizardUnblockRevisionsPage_specifyRevisions);

        try {
            int[] weights = new int[3];
            weights[0] = settings.getInt("MergeWizardRevisionsPageWeights0"); //$NON-NLS-1$
            weights[1] = settings.getInt("MergeWizardRevisionsPageWeights1"); //$NON-NLS-1$
            weights[2] = settings.getInt("MergeWizardRevisionsPageWeights2"); //$NON-NLS-1$
            verticalSash.setWeights(weights);
        } catch (Exception e) {
        }

        compareViewerPane = new CompareViewerSwitchingPane(horizontalSash, SWT.BORDER | SWT.FLAT) {
            protected Viewer getViewer(Viewer oldViewer, Object input) {
                CompareConfiguration cc = compareInput.getCompareConfiguration();
                cc.setLeftEditable(false);
                cc.setRightEditable(false);
                cc.setLeftLabel(compareInput.getLeftLabel());
                cc.setRightLabel(compareInput.getRightLabel());
                return CompareUI.findContentViewer(oldViewer, input, this, cc);
            }
        };
        compareViewerPane.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        try {
            int[] weights = new int[2];
            weights[0] = settings.getInt("MergeWizardRevisionsPageWeightsHorizontal0"); //$NON-NLS-1$
            weights[1] = settings.getInt("MergeWizardRevisionsPageWeightsHorizontal1"); //$NON-NLS-1$
            horizontalSash.setWeights(weights);
        } catch (Exception e) {
        }

        if (!showCompare) {
            horizontalSash.setMaximizedControl(verticalSash);
        } else {
            showCompareButton.setSelection(true);
        }

        setControl(composite);
    }

    public boolean canFinish() {
        return getSelectedRevisions().length > 0;
    }

    public void setVisible(boolean visible) {
        super.setVisible(visible);
        if (visible) {
            if (fromUrl == null || !fromUrl.equals(standardPage.getFromUrl()))
                refresh();

            // This is a hack to get around an initial sorting problem on OSx.
            if (!pageShown) {
                pageShown = true;
                historyTableProvider.setSortColumn(tableHistoryViewer, 0);
                historyTableProvider.setSortColumn(tableHistoryViewer, 0);
            }
        }
    }

    private void setCompareInput(final SVNCompareEditorInput input, boolean run)
            throws InterruptedException, InvocationTargetException {
        if (run) {
            input.run(new NullProgressMonitor());
        }
        compareViewerPane.setInput(null);
        compareViewerPane.setInput(input.getCompareResult());
    }

    public void showComparePane(boolean showCompare) {
        this.showCompare = showCompare;
        if (showCompare) {
            horizontalSash.setMaximizedControl(null);
        } else {
            horizontalSash.setMaximizedControl(verticalSash);
        }

    }

    private void refresh() {
        IRunnableWithProgress runnable = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                try {
                    monitor.setTaskName(Messages.MergeWizardUnblockRevisionsPage_retrievingRemoteResource);
                    monitor.beginTask(Messages.MergeWizardUnblockRevisionsPage_retrievingRemoteResource, 2);
                    monitor.worked(1);
                    remoteResource = repositoryLocation.getRemoteFile(new SVNUrl(fromUrl));
                    monitor.worked(1);
                    monitor.done();
                } catch (Exception e) {
                    Activator.handleError(e);
                }
            }
        };
        setPageComplete(false);
        setErrorMessage(null);
        fromUrl = standardPage.getFromUrl();
        ISVNMergeInfo mergeInfo = standardPage.getMergeInfo();
        if (mergeInfo == null) {
            entries = new ILogEntry[0];
            return;
        }
        revisionRanges = mergeInfo.getRevisionRange(fromUrl);
        remoteResource = null;
        try {
            getContainer().run(false, false, runnable);
            if (remoteResource == null) {
                setErrorMessage(Messages.MergeWizardUnblockRevisionsPage_errorRetrievingRemoteResource);
                entries = new ILogEntry[0];
            } else
                getLogEntries();
        } catch (Exception e) {
            setErrorMessage(e.getMessage());
            entries = new ILogEntry[0];
        }
        if (tableHistoryViewer.getInput() == null)
            tableHistoryViewer.setInput(fromUrl);
        else
            tableHistoryViewer.refresh();

        setPageComplete(canFinish());
    }

    private void getLogEntries() {
        entryArray = new ArrayList();
        IRunnableWithProgress runnable = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                try {
                    monitor.setTaskName(Messages.MergeWizardUnblockRevisionsPage_retrievingRevisionLogInfo);
                    monitor.beginTask(Messages.MergeWizardUnblockRevisionsPage_retrievingRevisionLogInfo, 3);
                    if (SVNUIPlugin.getPlugin().getPreferenceStore()
                            .getBoolean(ISVNUIConstants.PREF_SHOW_TAGS_IN_REMOTE))
                        tagManager = new AliasManager(remoteResource.getUrl());
                    SVNRevision pegRevision = remoteResource.getRevision();
                    monitor.worked(1);
                    for (int i = 0; i < revisionRanges.length; i++) {
                        rangeEntries = getLogEntries(pegRevision, revisionRanges[i].getFromRevision(),
                                revisionRanges[i].getToRevision(), true, 0, tagManager, true);
                        monitor.worked(1);
                        for (int j = 0; j < rangeEntries.length; j++) {
                            entryArray.add(rangeEntries[j]);
                        }
                    }

                    entries = new ILogEntry[entryArray.size()];
                    entryArray.toArray(entries);
                } catch (Exception e) {
                    setErrorMessage(e.getMessage());
                    entries = new ILogEntry[0];
                }
                monitor.worked(1);
                monitor.done();
            }
        };
        try {
            getContainer().run(false, false, runnable);
        } catch (Exception e1) {
            Activator.handleError(e1);
        }
        setErrorMessage(standardPage.getErrorMessage());
    }

    private SVNRevision.Number[] getAll() {
        allRevisions = new SVNRevision.Number[entryArray.size()];
        int i = 0;
        Iterator iter = entryArray.iterator();
        while (iter.hasNext()) {
            ILogEntry logEntry = (ILogEntry) iter.next();
            allRevisions[i++] = logEntry.getRevision();
        }
        return allRevisions;
    }

    protected ILogEntry[] getLogEntries(SVNRevision pegRevision, SVNRevision revisionStart, SVNRevision revisionEnd,
            boolean stopOnCopy, long limit, AliasManager tagManager, boolean includeMergedRevisions)
            throws TeamException {
        GetLogsCommand logCmd = new GetLogsCommand(remoteResource, pegRevision, revisionStart, revisionEnd,
                stopOnCopy, limit, tagManager, includeMergedRevisions);
        logCmd.run(null);
        return logCmd.getLogEntries();
    }

    private SVNRevision.Number[] getSelectedRevisions() {
        ArrayList selectedEntries = new ArrayList();
        TableItem[] items = tableHistoryViewer.getTable().getItems();
        for (int i = 0; i < items.length; i++) {
            if (items[i].getChecked()) {
                ILogEntry entry = (ILogEntry) items[i].getData();
                selectedEntries.add(entry.getRevision());
            }
        }
        SVNRevision.Number[] entryArray = new SVNRevision.Number[selectedEntries.size()];
        selectedEntries.toArray(entryArray);
        return entryArray;
    }

    public SVNRevisionRange[] getRevisions() {
        SVNRevisionRange[] revisionRanges = SVNRevisionRange.getRevisions(getSelectedRevisions(), getAll());
        SVNRevisionRange[] reversedRevisionRanges = new SVNRevisionRange[revisionRanges.length];
        for (int i = 0; i < revisionRanges.length; i++) {
            reversedRevisionRanges[i] = new SVNRevisionRange(revisionRanges[i].getToRevision(),
                    revisionRanges[i].getFromRevision());
        }
        return reversedRevisionRanges;
    }

    public void dispose() {
        if (pageShown) {
            int[] weights = verticalSash.getWeights();
            for (int i = 0; i < weights.length; i++) {
                settings.put("MergeWizardRevisionsPageWeights" + i, weights[i]); //$NON-NLS-1$ 
            }
            weights = horizontalSash.getWeights();
            for (int i = 0; i < weights.length; i++) {
                settings.put("MergeWizardRevisionsPageWeightsHorizontal" + i, weights[i]); //$NON-NLS-1$ 
            }
        }
        super.dispose();
    }

    public void setFromUrl(String fromUrl) {
        this.fromUrl = fromUrl;
    }

    private void compareRevisions() {
        IStructuredSelection sel = (IStructuredSelection) changePathsViewer.getSelection();
        Object sel0 = sel.getFirstElement();
        if (sel0 instanceof LogEntryChangePath) {
            LogEntryChangePath logEntryChangePath = (LogEntryChangePath) sel0;
            try {
                if (!logEntryChangePath.getRemoteResource().isContainer()) {
                    ISVNRemoteResource left = logEntryChangePath.getRemoteResource();
                    compareInput = compareInputMap.get(left.getUrl().toString() + left.getRevision());
                    boolean run = compareInput == null;
                    if (compareInput == null) {
                        SVNRevision.Number selectedRevision = (SVNRevision.Number) left.getRevision();
                        SVNRevision.Number previousRevision = new SVNRevision.Number(
                                selectedRevision.getNumber() - 1);
                        ISVNRemoteResource right = new RemoteFile(left.getRepository(), left.getUrl(),
                                previousRevision);
                        compareInput = new SVNCompareEditorInput(new ResourceEditionNode(left),
                                new ResourceEditionNode(right));
                        compareInputMap.put(left.getUrl().toString() + left.getRevision(), compareInput);
                    }
                    setCompareInput(compareInput, run);
                    showComparePane(true);
                }
            } catch (Exception e) {
                MessageDialog.openError(getShell(), Messages.MergeWizardRevisionsPage_5, e.getMessage());
            }
        }
    }

    private class ToggleSelectionAction extends Action {

        public ToggleSelectionAction() {
            super();
            setText("Toggle selection"); //$NON-NLS-1$       
        }

        public void run() {
            TableItem[] items = tableHistoryViewer.getTable().getSelection();
            for (int i = 0; i < items.length; i++)
                items[i].setChecked(!items[i].getChecked());
            setPageComplete(canFinish());
        }

    }

    static class ChangePathsTreeContentProvider implements ITreeContentProvider {

        ChangePathsTreeContentProvider() {
        }

        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof HistoryFolder) {
                return ((HistoryFolder) parentElement).getChildren();
            }
            return null;
        }

        public Object getParent(Object element) {
            return null;
        }

        public boolean hasChildren(Object element) {
            if (element instanceof HistoryFolder) {
                HistoryFolder folder = (HistoryFolder) element;
                return folder.getChildren().length > 0;
            }
            return false;
        }

        public Object[] getElements(Object inputElement) {
            ILogEntry logEntry = (ILogEntry) inputElement;
            return getGroups(logEntry.getLogEntryChangePaths());
        }

        private Object[] getGroups(LogEntryChangePath[] changePaths) {
            // 1st pass. Collect folder names
            Set folderNames = new HashSet();
            for (int i = 0; i < changePaths.length; i++) {
                folderNames.add(getFolderName(changePaths[i]));
            }

            // 2nd pass. Sorting out explicitly changed folders
            TreeMap folders = new TreeMap();
            for (int i = 0; i < changePaths.length; i++) {
                LogEntryChangePath changePath = changePaths[i];
                String path = changePath.getPath();
                if (folderNames.contains(path)) {
                    // changed folder
                    HistoryFolder folder = (HistoryFolder) folders.get(path);
                    if (folder == null) {
                        folder = new HistoryFolder(changePath);
                        folders.put(path, folder);
                    }
                } else {
                    // changed resource
                    path = getFolderName(changePath);
                    HistoryFolder folder = (HistoryFolder) folders.get(path);
                    if (folder == null) {
                        folder = new HistoryFolder(path);
                        folders.put(path, folder);
                    }
                    folder.add(changePath);
                }
            }

            return folders.values().toArray(new Object[folders.size()]);
        }

        private String getFolderName(LogEntryChangePath changePath) {
            String path = changePath.getPath();
            int n = path.lastIndexOf('/');
            return n > -1 ? path.substring(0, n) : path;
        }

        public void dispose() {
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

    }

}