org.eclipse.egit.ui.internal.fetch.FetchGerritChangePage.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.egit.ui.internal.fetch.FetchGerritChangePage.java

Source

/*******************************************************************************
 * Copyright (c) 2010 SAP AG.
 * 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:
 *    Mathias Kinzler (SAP AG) - initial implementation
 *******************************************************************************/
package org.eclipse.egit.ui.internal.fetch;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egit.core.op.CreateLocalBranchOperation;
import org.eclipse.egit.core.op.ListRemoteOperation;
import org.eclipse.egit.core.op.TagOperation;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.branch.BranchOperationUI;
import org.eclipse.egit.ui.internal.dialogs.CheckoutConflictDialog;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CheckoutResult;
import org.eclipse.jgit.api.CheckoutResult.Status;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TagBuilder;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

/**
 * Fetch a change from Gerrit
 */
public class FetchGerritChangePage extends WizardPage {
    private static final String FETCH_GERRIT_CHANGE_PAGE_SECTION = "FetchGerritChangePage"; //$NON-NLS-1$

    private static final String LAST_URI_POSTFIX = ".lastUri"; //$NON-NLS-1$

    private final Repository repository;

    private final IDialogSettings settings;

    private final String lastUriKey;

    private Combo uriCombo;

    private List<Change> changeRefs;

    private Text refText;

    private Button createBranch;

    private Button createTag;

    private Button checkout;

    private Button dontCheckout;

    private Label tagTextlabel;

    private Text tagText;

    private Label branchTextlabel;

    private Text branchText;

    private String refName;

    private Composite warningAdditionalRefNotActive;

    private Button activateAdditionalRefs;

    /**
     * @param repository
     * @param refName initial value for the ref field
     */
    public FetchGerritChangePage(Repository repository, String refName) {
        super(FetchGerritChangePage.class.getName());
        this.repository = repository;
        this.refName = refName;
        setTitle(NLS.bind(UIText.FetchGerritChangePage_PageTitle,
                Activator.getDefault().getRepositoryUtil().getRepositoryName(repository)));
        setMessage(UIText.FetchGerritChangePage_PageMessage);
        settings = getDialogSettings();
        lastUriKey = repository + LAST_URI_POSTFIX;
    }

    protected IDialogSettings getDialogSettings() {
        IDialogSettings s = Activator.getDefault().getDialogSettings();
        IDialogSettings section = s.getSection(FETCH_GERRIT_CHANGE_PAGE_SECTION);
        if (section == null)
            section = s.addNewSection(FETCH_GERRIT_CHANGE_PAGE_SECTION);
        return section;
    }

    public void createControl(Composite parent) {
        Clipboard clipboard = new Clipboard(parent.getDisplay());
        String clipText = (String) clipboard.getContents(TextTransfer.getInstance());
        String defaultUri = null;
        String defaultCommand = null;
        String defaultChange = null;
        if (clipText != null) {
            final String pattern = "git fetch (\\w+:\\S+) (refs/changes/\\d+/\\d+/\\d+) && git (\\w+) FETCH_HEAD"; //$NON-NLS-1$
            Matcher matcher = Pattern.compile(pattern).matcher(clipText);
            if (matcher.matches()) {
                defaultUri = matcher.group(1);
                defaultChange = matcher.group(2);
                defaultCommand = matcher.group(3);
            }
        }
        Composite main = new Composite(parent, SWT.NONE);
        main.setLayout(new GridLayout(2, false));
        GridDataFactory.fillDefaults().grab(true, true).applyTo(main);
        new Label(main, SWT.NONE).setText(UIText.FetchGerritChangePage_UriLabel);
        uriCombo = new Combo(main, SWT.DROP_DOWN);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(uriCombo);
        uriCombo.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                changeRefs = null;
            }
        });
        new Label(main, SWT.NONE).setText(UIText.FetchGerritChangePage_ChangeLabel);
        refText = new Text(main, SWT.BORDER);
        if (defaultChange != null)
            refText.setText(defaultChange);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(refText);
        addRefContentProposalToText(refText);

        Group checkoutGroup = new Group(main, SWT.SHADOW_ETCHED_IN);
        checkoutGroup.setLayout(new GridLayout(2, false));
        GridDataFactory.fillDefaults().span(2, 1).grab(true, true).applyTo(checkoutGroup);
        checkoutGroup.setText(UIText.FetchGerritChangePage_AfterFetchGroup);

        // radio: create local branch
        createBranch = new Button(checkoutGroup, SWT.RADIO);
        GridDataFactory.fillDefaults().span(2, 1).applyTo(createBranch);
        createBranch.setText(UIText.FetchGerritChangePage_LocalBranchRadio);
        createBranch.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                checkPage();
            }
        });

        branchTextlabel = new Label(checkoutGroup, SWT.NONE);
        GridDataFactory.defaultsFor(branchTextlabel).exclude(false).applyTo(branchTextlabel);
        branchTextlabel.setText(UIText.FetchGerritChangePage_BranchNameText);
        branchText = new Text(checkoutGroup, SWT.SINGLE | SWT.BORDER);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(branchText);
        branchText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                checkPage();
            }
        });

        // radio: create tag
        createTag = new Button(checkoutGroup, SWT.RADIO);
        GridDataFactory.fillDefaults().span(2, 1).applyTo(createTag);
        createTag.setText(UIText.FetchGerritChangePage_TagRadio);
        createTag.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                checkPage();
            }
        });

        tagTextlabel = new Label(checkoutGroup, SWT.NONE);
        GridDataFactory.defaultsFor(tagTextlabel).exclude(true).applyTo(tagTextlabel);
        tagTextlabel.setText(UIText.FetchGerritChangePage_TagNameText);
        tagText = new Text(checkoutGroup, SWT.SINGLE | SWT.BORDER);
        GridDataFactory.fillDefaults().exclude(true).grab(true, false).applyTo(tagText);
        tagText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                checkPage();
            }
        });

        // radio: checkout FETCH_HEAD
        checkout = new Button(checkoutGroup, SWT.RADIO);
        GridDataFactory.fillDefaults().span(2, 1).applyTo(checkout);
        checkout.setText(UIText.FetchGerritChangePage_CheckoutRadio);
        checkout.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                checkPage();
            }
        });

        // radio: don't checkout
        dontCheckout = new Button(checkoutGroup, SWT.RADIO);
        GridDataFactory.fillDefaults().span(2, 1).applyTo(checkout);
        dontCheckout.setText(UIText.FetchGerritChangePage_UpdateRadio);
        dontCheckout.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                checkPage();
            }
        });

        if ("checkout".equals(defaultCommand)) //$NON-NLS-1$
            checkout.setSelection(true);
        else
            createBranch.setSelection(true);

        warningAdditionalRefNotActive = new Composite(main, SWT.NONE);
        GridDataFactory.fillDefaults().span(2, 1).grab(true, false).exclude(true)
                .applyTo(warningAdditionalRefNotActive);
        warningAdditionalRefNotActive.setLayout(new GridLayout(2, false));
        warningAdditionalRefNotActive.setVisible(false);

        activateAdditionalRefs = new Button(warningAdditionalRefNotActive, SWT.CHECK);
        activateAdditionalRefs.setText(UIText.FetchGerritChangePage_ActivateAdditionalRefsButton);
        activateAdditionalRefs.setToolTipText(UIText.FetchGerritChangePage_ActivateAdditionalRefsTooltip);

        refText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                Change change = Change.fromRef(refText.getText());
                if (change != null) {
                    branchText.setText(NLS.bind(UIText.FetchGerritChangePage_SuggestedRefNamePattern,
                            change.getChangeNumber(), change.getPatchSetNumber()));
                    tagText.setText(branchText.getText());
                } else {
                    branchText.setText(""); //$NON-NLS-1$
                    tagText.setText(""); //$NON-NLS-1$
                }
                checkPage();
            }
        });

        // get all available URIs from the repository
        SortedSet<String> uris = new TreeSet<String>();
        try {
            for (RemoteConfig rc : RemoteConfig.getAllRemoteConfigs(repository.getConfig())) {
                if (rc.getURIs().size() > 0)
                    uris.add(rc.getURIs().get(0).toPrivateString());
                for (URIish u : rc.getPushURIs())
                    uris.add(u.toPrivateString());

            }
        } catch (URISyntaxException e) {
            Activator.handleError(e.getMessage(), e, false);
            setErrorMessage(e.getMessage());
        }
        for (String aUri : uris)
            uriCombo.add(aUri);
        if (defaultUri != null)
            uriCombo.setText(defaultUri);
        else
            selectLastUsedUri();
        refText.setFocus();
        Dialog.applyDialogFont(main);
        setControl(main);
        checkPage();
    }

    private void storeLastUsedUri(String uri) {
        settings.put(lastUriKey, uri.trim());
    }

    private void selectLastUsedUri() {
        String lastUri = settings.get(lastUriKey);
        if (lastUri != null) {
            int i = uriCombo.indexOf(lastUri);
            if (i != -1) {
                uriCombo.select(i);
                return;
            }
        }
        uriCombo.select(0);
    }

    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);
        if (visible && refName != null)
            refText.setText(refName);
    }

    private void checkPage() {
        boolean createBranchSelected = createBranch.getSelection();
        branchText.setEnabled(createBranchSelected);
        branchText.setVisible(createBranchSelected);
        branchTextlabel.setVisible(createBranchSelected);
        GridData gd = (GridData) branchText.getLayoutData();
        gd.exclude = !createBranchSelected;
        gd = (GridData) branchTextlabel.getLayoutData();
        gd.exclude = !createBranchSelected;

        boolean createTagSelected = createTag.getSelection();
        tagText.setEnabled(createTagSelected);
        tagText.setVisible(createTagSelected);
        tagTextlabel.setVisible(createTagSelected);
        gd = (GridData) tagText.getLayoutData();
        gd.exclude = !createTagSelected;
        gd = (GridData) tagTextlabel.getLayoutData();
        gd.exclude = !createTagSelected;
        branchText.getParent().layout(true);

        boolean showActivateAdditionalRefs = false;
        showActivateAdditionalRefs = (checkout.getSelection() || dontCheckout.getSelection()) && !Activator
                .getDefault().getPreferenceStore().getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS);

        gd = (GridData) warningAdditionalRefNotActive.getLayoutData();
        gd.exclude = !showActivateAdditionalRefs;
        warningAdditionalRefNotActive.setVisible(showActivateAdditionalRefs);
        warningAdditionalRefNotActive.getParent().layout(true);

        setErrorMessage(null);
        try {
            if (refText.getText().length() > 0) {
                Change change = Change.fromRef(refText.getText());
                if (change == null) {
                    setErrorMessage(UIText.FetchGerritChangePage_MissingChangeMessage);
                    return;
                }
            } else {
                setErrorMessage(UIText.FetchGerritChangePage_MissingChangeMessage);
                return;
            }

            boolean emptyRefName = (createBranchSelected && branchText.getText().length() == 0)
                    || (createTagSelected && tagText.getText().length() == 0);
            if (emptyRefName) {
                setErrorMessage(UIText.FetchGerritChangePage_ProvideRefNameMessage);
                return;
            }

            boolean existingRefName = (createBranchSelected && repository.getRef(branchText.getText()) != null)
                    || (createTagSelected && repository.getRef(tagText.getText()) != null);
            if (existingRefName) {
                setErrorMessage(NLS.bind(UIText.FetchGerritChangePage_ExistingRefMessage, branchText.getText()));
                return;
            }
        } catch (IOException e1) {
            // ignore here
        } finally {
            setPageComplete(getErrorMessage() == null);
        }
    }

    private List<Change> getRefsForContentAssist() throws InvocationTargetException, InterruptedException {
        if (changeRefs == null) {
            final String uriText = uriCombo.getText();
            getWizard().getContainer().run(true, true, new IRunnableWithProgress() {
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    ListRemoteOperation listOp;
                    try {
                        listOp = new ListRemoteOperation(repository, new URIish(uriText), Activator.getDefault()
                                .getPreferenceStore().getInt(UIPreferences.REMOTE_CONNECTION_TIMEOUT));
                    } catch (URISyntaxException e) {
                        throw new InvocationTargetException(e);
                    }

                    listOp.run(monitor);
                    changeRefs = new ArrayList<Change>();
                    for (Ref ref : listOp.getRemoteRefs()) {
                        Change change = Change.fromRef(ref.getName());
                        if (change != null)
                            changeRefs.add(change);
                    }
                    Collections.sort(changeRefs, new Comparator<Change>() {
                        public int compare(Change o1, Change o2) {
                            // change number descending
                            int changeDiff = o2.changeNumber.compareTo(o1.changeNumber);
                            if (changeDiff == 0)
                                // patch set number descending
                                changeDiff = o2.getPatchSetNumber().compareTo(o1.getPatchSetNumber());
                            return changeDiff;
                        }
                    });
                }
            });
        }
        return changeRefs;
    }

    boolean doFetch() {
        try {
            final RefSpec spec = new RefSpec().setSource(refText.getText()).setDestination(Constants.FETCH_HEAD);
            final String uri = uriCombo.getText();
            final boolean doCheckout = checkout.getSelection();
            final boolean doCreateTag = createTag.getSelection();
            final boolean doCreateBranch = createBranch.getSelection();
            final boolean doActivateAdditionalRefs = (checkout.getSelection() || dontCheckout.getSelection())
                    && activateAdditionalRefs.getSelection();
            final String textForTag = tagText.getText();
            final String textForBranch = branchText.getText();

            getWizard().getContainer().run(true, true, new IRunnableWithProgress() {
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    int totalWork = 1;
                    if (doCheckout)
                        totalWork++;
                    if (doCreateTag || doCreateBranch)
                        totalWork++;
                    monitor.beginTask(UIText.FetchGerritChangePage_GetChangeTaskName, totalWork);

                    try {
                        RevCommit commit = fetchChange(uri, spec, monitor);

                        if (doCreateTag) {
                            createTag(spec, textForTag, commit, monitor);
                        }
                        if (doCreateBranch) {
                            createBranch(textForBranch, commit, monitor);
                        }
                        if (doCheckout || doCreateTag) {
                            checkout(commit, monitor);
                        }
                        if (doActivateAdditionalRefs) {
                            activateAdditionalRefs();
                        }
                        storeLastUsedUri(uri);
                    } catch (RuntimeException e) {
                        throw e;
                    } catch (Exception e) {
                        throw new InvocationTargetException(e);
                    } finally {
                        monitor.done();
                    }
                }
            });
        } catch (InvocationTargetException e) {
            Activator.handleError(e.getCause().getMessage(), e.getCause(), true);
            return false;
        } catch (InterruptedException e) {
            // just return
        }
        return true;
    }

    private RevCommit fetchChange(String uri, RefSpec spec, IProgressMonitor monitor)
            throws CoreException, URISyntaxException, IOException {
        int timeout = Activator.getDefault().getPreferenceStore().getInt(UIPreferences.REMOTE_CONNECTION_TIMEOUT);

        List<RefSpec> specs = new ArrayList<RefSpec>(1);
        specs.add(spec);

        String taskName = NLS.bind(UIText.FetchGerritChangePage_FetchingTaskName, spec.getSource());
        monitor.setTaskName(taskName);
        FetchResult fetchRes = new FetchOperationUI(repository, new URIish(uri), specs, timeout, false)
                .execute(monitor);

        monitor.worked(1);
        return new RevWalk(repository).parseCommit(fetchRes.getAdvertisedRef(spec.getSource()).getObjectId());
    }

    private void createTag(final RefSpec spec, final String textForTag, RevCommit commit, IProgressMonitor monitor)
            throws CoreException {
        monitor.setTaskName(UIText.FetchGerritChangePage_CreatingTagTaskName);
        final TagBuilder tag = new TagBuilder();
        PersonIdent personIdent = new PersonIdent(repository);

        tag.setTag(textForTag);
        tag.setTagger(personIdent);
        tag.setMessage(NLS.bind(UIText.FetchGerritChangePage_GeneratedTagMessage, spec.getSource()));
        tag.setObjectId(commit);
        new TagOperation(repository, tag, false).execute(monitor);
        monitor.worked(1);
    }

    private void createBranch(final String textForBranch, RevCommit commit, IProgressMonitor monitor)
            throws CoreException, GitAPIException {
        monitor.setTaskName(UIText.FetchGerritChangePage_CreatingBranchTaskName);
        CreateLocalBranchOperation bop = new CreateLocalBranchOperation(repository, textForBranch, commit);
        bop.execute(monitor);
        CheckoutCommand co = new Git(repository).checkout();
        try {
            co.setName(textForBranch).call();
        } catch (CheckoutConflictException e) {
            final CheckoutResult result = co.getResult();

            if (result.getStatus() == Status.CONFLICTS) {
                final Shell shell = getWizard().getContainer().getShell();

                shell.getDisplay().asyncExec(new Runnable() {
                    public void run() {
                        new CheckoutConflictDialog(shell, repository, result.getConflictList()).open();
                    }
                });
            }
        }
        monitor.worked(1);
    }

    private void checkout(RevCommit commit, IProgressMonitor monitor) throws CoreException {
        monitor.setTaskName(UIText.FetchGerritChangePage_CheckingOutTaskName);
        BranchOperationUI.checkout(repository, commit.name()).run(monitor);

        monitor.worked(1);
    }

    private void activateAdditionalRefs() {
        // do this in the UI thread as it results in a
        // refresh() on the history page
        getContainer().getShell().getDisplay().asyncExec(new Runnable() {
            public void run() {
                Activator.getDefault().getPreferenceStore()
                        .setValue(UIPreferences.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS, true);
            }
        });
    }

    private void addRefContentProposalToText(final Text textField) {
        KeyStroke stroke;
        try {
            stroke = KeyStroke.getInstance("CTRL+SPACE"); //$NON-NLS-1$
            UIUtils.addBulbDecorator(textField,
                    NLS.bind(UIText.FetchGerritChangePage_ContentAssistTooltip, stroke.format()));
        } catch (ParseException e1) {
            Activator.handleError(e1.getMessage(), e1, false);
            stroke = null;
        }

        IContentProposalProvider cp = new IContentProposalProvider() {
            public IContentProposal[] getProposals(String contents, int position) {
                List<IContentProposal> resultList = new ArrayList<IContentProposal>();

                // make the simplest possible pattern check: allow "*"
                // for multiple characters
                String patternString = contents;
                // ignore spaces in the beginning
                while (patternString.length() > 0 && patternString.charAt(0) == ' ')
                    patternString = patternString.substring(1);

                // we quote the string as it may contain spaces
                // and other stuff colliding with the Pattern
                patternString = Pattern.quote(patternString);

                patternString = patternString.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$

                // make sure we add a (logical) * at the end
                if (!patternString.endsWith(".*")) //$NON-NLS-1$
                    patternString = patternString + ".*"; //$NON-NLS-1$

                // let's compile a case-insensitive pattern (assumes ASCII only)
                Pattern pattern;
                try {
                    pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
                } catch (PatternSyntaxException e) {
                    pattern = null;
                }

                List<Change> proposals;
                try {
                    proposals = getRefsForContentAssist();
                } catch (InvocationTargetException e) {
                    Activator.handleError(e.getMessage(), e, false);
                    return null;
                } catch (InterruptedException e) {
                    return null;
                }

                if (proposals != null)
                    for (final Change ref : proposals) {
                        if (pattern != null && !pattern.matcher(ref.getChangeNumber().toString()).matches())
                            continue;
                        IContentProposal propsal = new ChangeContentProposal(ref);
                        resultList.add(propsal);
                    }

                return resultList.toArray(new IContentProposal[resultList.size()]);
            }
        };

        ContentProposalAdapter adapter = new ContentProposalAdapter(textField, new TextContentAdapter(), cp, stroke,
                null);
        // set the acceptance style to always replace the complete content
        adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
    }

    private final static class Change {
        private final String refName;

        private final Integer changeNumber;

        private final Integer patchSetNumber;

        static Change fromRef(String refName) {
            try {
                if (!refName.startsWith("refs/changes/")) //$NON-NLS-1$
                    return null;
                String[] tokens = refName.substring(13).split("/"); //$NON-NLS-1$
                if (tokens.length != 3)
                    return null;
                Integer changeNumber = Integer.valueOf(tokens[1]);
                Integer patchSetNumber = Integer.valueOf(tokens[2]);
                return new Change(refName, changeNumber, patchSetNumber);
            } catch (NumberFormatException e) {
                // if we can't parse this, just return null
                return null;
            } catch (IndexOutOfBoundsException e) {
                // if we can't parse this, just return null
                return null;
            }
        }

        private Change(String refName, Integer changeNumber, Integer patchSetNumber) {
            this.refName = refName;
            this.changeNumber = changeNumber;
            this.patchSetNumber = patchSetNumber;
        }

        public String getRefName() {
            return refName;
        }

        public Integer getChangeNumber() {
            return changeNumber;
        }

        public Integer getPatchSetNumber() {
            return patchSetNumber;
        }

        /* (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return refName;
        }
    }

    private final static class ChangeContentProposal implements IContentProposal {
        private final Change myChange;

        ChangeContentProposal(Change change) {
            myChange = change;
        }

        public String getContent() {
            return myChange.getRefName();
        }

        public int getCursorPosition() {
            return 0;
        }

        public String getDescription() {
            return NLS.bind(UIText.FetchGerritChangePage_ContentAssistDescription, myChange.getPatchSetNumber(),
                    myChange.getChangeNumber());
        }

        public String getLabel() {
            return NLS.bind("{0} - {1}", myChange.getChangeNumber(), myChange.getPatchSetNumber()); //$NON-NLS-1$
        }

        /* (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return getContent();
        }
    }
}