Package org.eclipse.egit.ui.internal.components

Source Code of org.eclipse.egit.ui.internal.components.RefSpecPanel

/*******************************************************************************
* Copyright (C) 2008, 2012 Marek Zawirski <marek.zawirski@gmail.com> 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
*******************************************************************************/
package org.eclipse.egit.ui.internal.components;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.jface.fieldassist.ComboContentAdapter;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalListener;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.IElementComparer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.FetchConnection;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
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.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter;

/**
* This class provides universal panel for editing list of {@link RefSpec} -
* specifications for both push or fetch, depending on panel configuration.
* <p>
* It is intended to allow user easily edit specifications, supporting user with
* content assistant and giving feedback with error information as soon as
* possible. Component uses editable specifications table and panels for easy
* creation of new specifications basing on typical push/fetch schemes (like
* branch update, deletion, all branches update, saved configuration etc.).
* <p>
* The model of specifications list behind panel is accessible by public methods
* - giving both read and write access. Listener interface for handling changes
* in model is provided by {@link SelectionChangeListener}.
* <p>
* Typical class usage:
*
* <pre>
* // create panel for editing push-specifications
* RefSpecPanel panel = new RefSpecPanel(parent, true);
* // register model listener
* panel.addRefSpecPanelListener(listener);
*
* // provide information about local and remote refs
* panel.setRefsData(localRepo, remoteRefs, remoteName);
*
* // get result data
* List&lt;RefSpec&gt; result = panel.getRefSpecs();
* // further processing: push or save configuration...
* </pre>
*
* @see SelectionChangeListener
*/
public class RefSpecPanel {
  private static final String IMAGE_ADD = "ADD"; //$NON-NLS-1$

  private static final String IMAGE_DELETE = "DELETE"; //$NON-NLS-1$

  private static final String IMAGE_TRASH = "TRASH"; //$NON-NLS-1$

  private static final String IMAGE_CLEAR = "CLEAR"; //$NON-NLS-1$

  private static final int TABLE_PREFERRED_HEIGHT = 165;

  private static final int TABLE_PREFERRED_WIDTH = 560;

  private static final int COLUMN_MODE_WEIGHT = 23;

  private static final int COLUMN_SRC_WEIGHT = 40;

  private static final int COLUMN_DST_WEIGHT = 40;

  private static final int COLUMN_FORCE_WEIGHT = 30;

  private static final int COLUMN_REMOVE_WEIGHT = 20;

  private static boolean isDeleteRefSpec(final Object element) {
    return ((RefSpec) element).getSource() == null;
  }

  private static boolean isValidRefExpression(final String s) {
    if (RefSpec.isWildcard(s)) {
      // replace wildcard with some legal name just for checking
      return isValidRefExpression(s.substring(0, s.length() - 1) + 'X');
    } else
      return Repository.isValidRefName(s)
          || Repository.isValidRefName(Constants.R_HEADS + s)
          || Repository.isValidRefName(Constants.R_TAGS + s);
  }

  private static RefSpec setRefSpecSource(final RefSpec spec, final String src) {
    final String dst;
    if (RefSpec.isWildcard(src))
      dst = wildcardSpecComponent(spec.getDestination());
    else
      dst = unwildcardSpecComponent(spec.getDestination(), src);
    return spec.setSourceDestination(src, dst);
  }

  private static RefSpec setRefSpecDestination(final RefSpec spec,
      final String dst) {
    final String src;
    if (RefSpec.isWildcard(dst))
      src = wildcardSpecComponent(spec.getSource());
    else
      src = unwildcardSpecComponent(spec.getSource(), dst);
    return spec.setSourceDestination(src, dst);
  }

  private static String wildcardSpecComponent(final String comp) {
    final int i;
    if (RefSpec.isWildcard(comp))
      return comp;
    if (comp == null || (i = comp.lastIndexOf('/')) == -1) {
      // That's somewhat ugly. What better can we do here?
      return UIText.RefSpecPanel_refChooseSomeWildcard;
    }
    return comp.substring(0, i + 1) + '*';
  }

  private static String unwildcardSpecComponent(final String comp,
      final String other) {
    if (!RefSpec.isWildcard(comp))
      return comp;
    if (other == null || other.length() == 0)
      return ""; //$NON-NLS-1$
    final int i = other.lastIndexOf('/');
    return comp.substring(0, comp.length() - 1) + other.substring(i + 1);
  }

  private static List<RefContentProposal> createProposalsFilteredRemote(
      final List<RefContentProposal> proposals) {
    final List<RefContentProposal> result = new ArrayList<RefContentProposal>();
    for (final RefContentProposal p : proposals) {
      final String content = p.getContent();
      if (content.equals(Constants.HEAD)
          || content.startsWith(Constants.R_HEADS))
        result.add(p);
    }
    return result;
  }

  private static Image getDecorationImage(final String key) {
    return FieldDecorationRegistry.getDefault().getFieldDecoration(key)
        .getImage();
  }

  private static void setControlDecoration(final ControlDecoration control,
      final String imageKey, final String description) {
    control.setImage(getDecorationImage(imageKey));
    control.setDescriptionText(description);
    control.show();
  }

  private final List<RefSpec> specs = new ArrayList<RefSpec>();

  private final Composite panel;

  private TableViewer tableViewer;

  private CellEditor modeCellEditor;

  private CellEditor localRefCellEditor;

  private CellEditor remoteRefCellEditor;

  private CellEditor forceUpdateCellEditor;

  private CellEditor removeSpecCellEditor;

  private int srcColumnIndex;

  private Button removeAllSpecButton;

  private Button forceUpdateAllButton;

  private Button creationButton;

  private Button addConfiguredButton;

  private Button addTagsButton;

  private Button addBranchesButton;

  private ControlDecoration creationSrcDecoration;

  private ControlDecoration creationDstDecoration;

  private ControlDecoration deleteRefDecoration;

  private Combo creationSrcCombo;

  private Combo creationDstCombo;

  private Combo deleteRefCombo;

  private Button deleteButton;

  private Repository localDb;

  private RemoteConfig remoteConfig;

  private Set<String> localRefNames = Collections.emptySet();

  private Set<String> remoteRefNames = Collections.emptySet();

  private List<RefSpec> predefinedConfigured = Collections.emptyList();

  private RefSpec predefinedBranches = null;

  private final RefContentProposalProvider remoteProposalProvider;

  private final RefContentProposalProvider localProposalProvider;

  private ComboLabelingSupport creationSrcComboSupport;

  private ComboLabelingSupport creationDstComboSupport;

  private ComboLabelingSupport deleteRefComboSupport;

  private final boolean pushSpecs;

  private final List<SelectionChangeListener> listeners = new LinkedList<SelectionChangeListener>();

  private final ImageRegistry imageRegistry;

  private boolean matchingAnyRefs;

  private RefSpec invalidSpec;

  private RefSpec invalidSpecSameDst;

  private String errorMessage;

  private Color errorBackgroundColor;

  private Color errorTextColor;

  /**
   * Create a new panel and install it on a provided composite. Panel is
   * created either for editing push or fetch specifications - this setting
   * can't be changed later, after constructing object.
   * <p>
   * Panel is created with an empty model, with no provided assistant. It
   * can't be used by user until
   * {@link #setAssistanceData(Repository, Collection, RemoteConfig)} method is
   * called, and to this time is disabled.
   *
   * @param parent
   *            parent control for panel.
   * @param pushSpecs
   *            true if panel is used for editing push specifications, false
   *            if panel is used for editing fetch specifications.
   */
  public RefSpecPanel(final Composite parent, final boolean pushSpecs) {
    this.pushSpecs = pushSpecs;
    this.localProposalProvider = new RefContentProposalProvider(pushSpecs);
    this.remoteProposalProvider = new RefContentProposalProvider(false);
    this.imageRegistry = new ImageRegistry(parent.getDisplay());

    panel = new Composite(parent, SWT.NONE);
    panel.setLayout(new GridLayout());

    safeCreateResources();

    createCreationPanel();
    if (pushSpecs)
      createDeleteCreationPanel();
    createPredefinedCreationPanel();
    createTableGroup();

    addRefSpecTableListener(new SelectionChangeListener() {
      public void selectionChanged() {
        validateSpecs();
      }
    });
    setEnable(false);
  }

  /**
   * Enable or disable panel controls.
   *
   * @param enable
   *            true to enable panel, false to disable.
   */
  public void setEnable(final boolean enable) {
    getControl().setEnabled(enable);
  }

  /**
   * Set information needed for assisting user with entering data and
   * validating user input. This method automatically enables the panel.
   *
   * @param localRepo
   *            local repository where specifications will be applied.
   * @param remoteRefs
   *            collection of remote refs as advertised by remote repository.
   *            Typically they are collected by {@link FetchConnection}
   *            implementation.
   * @param config
   */
  public void setAssistanceData(final Repository localRepo,
      final Collection<Ref> remoteRefs, RemoteConfig config) {
    this.localDb = localRepo;
        this.remoteConfig = config;
    final List<RefContentProposal> remoteProposals = createContentProposals(
        remoteRefs, null);
    remoteProposalProvider.setProposals(remoteProposals);
    remoteRefNames = new HashSet<String>();
    for (final RefContentProposal p : remoteProposals)
      remoteRefNames.add(p.getContent());

    Ref HEAD = null;
    try {
      HEAD = localDb.getRef(Constants.HEAD);
    } catch (IOException e) {
      Activator.logError("Couldn't read HEAD from local repository", e); //$NON-NLS-1$
    }
    final List<RefContentProposal> localProposals = createContentProposals(
        localDb.getAllRefs().values(), HEAD);
    localProposalProvider.setProposals(localProposals);
    localRefNames = new HashSet<String>();
    for (final RefContentProposal ref : localProposals)
      localRefNames.add(ref.getContent());

    final List<RefContentProposal> localFilteredProposals = createProposalsFilteredLocal(localProposals);
    final List<RefContentProposal> remoteFilteredProposals = createProposalsFilteredRemote(remoteProposals);

    if (pushSpecs) {
      creationSrcComboSupport.setProposals(localFilteredProposals);
      creationDstComboSupport.setProposals(remoteFilteredProposals);
    } else {
      creationSrcComboSupport.setProposals(remoteFilteredProposals);
      creationDstComboSupport.setProposals(localFilteredProposals);
    }
    validateCreationPanel();

    if (pushSpecs) {
      deleteRefComboSupport.setProposals(remoteFilteredProposals);
      validateDeleteCreationPanel();
    }

    if (remoteConfig == null)
      predefinedConfigured = Collections.emptyList();
    else {
      if (pushSpecs)
        predefinedConfigured = remoteConfig.getPushRefSpecs();
      else
        predefinedConfigured = remoteConfig.getFetchRefSpecs();
      for (final RefSpec spec : predefinedConfigured)
        addRefSpec(spec);
    }

    updateAddPredefinedButton(addConfiguredButton, predefinedConfigured);
    if (pushSpecs)
      predefinedBranches = Transport.REFSPEC_PUSH_ALL;
    else {
      final String r;
      if (remoteConfig == null)
        r = UIText.RefSpecPanel_refChooseRemoteName;
      else
        r = remoteConfig.getName();
      predefinedBranches = new RefSpec("refs/heads/*:refs/remotes/" //$NON-NLS-1$
          + r + "/*"); //$NON-NLS-1$
    }
    updateAddPredefinedButton(addBranchesButton, predefinedBranches);
    setEnable(true);
  }

  /**
   * @return underlying control for this panel.
   */
  public Control getControl() {
    return panel;
  }

  /**
   * Return current list of specifications of this panel.
   * <p>
   * This method should be called only from the UI thread.
   *
   * @return unmodifiable view of specifications list as edited by user in
   *         this panel. Note that this view underlying model may change -
   *         create a copy if needed.
   */
  public List<RefSpec> getRefSpecs() {
    return Collections.unmodifiableList(specs);
  }

  /**
   * @return true if specifications list is empty, false otherwise.
   */
  public boolean isEmpty() {
    return getRefSpecs().isEmpty();
  }

  /**
   * @return true if specifications match any ref(s) in source repository -
   *         resolve to concrete ref updates, false otherwise. For non empty
   *         specifications list, false value is possible only in case of
   *         specifications with wildcards.
   */
  public boolean isMatchingAnyRefs() {
    return matchingAnyRefs;
  }

  /**
   * Add provided specification to this panel. Panel view is automatically
   * refreshed, model is revalidated.
   * <p>
   * Note that the same reference can't be added twice to the panel, while two
   * or more equals RefSpec (in terms of equals method) can be - likely
   * causing validation error.
   * <p>
   * This method should be called only from the UI thread.
   *
   * @param spec
   *            specification to add.
   * @throws IllegalArgumentException
   *             if specification with same reference already exists in panel.
   */
  public void addRefSpec(final RefSpec spec) {
    final int i = indexOfSpec(spec);
    if (i != -1)
      throw new IllegalArgumentException("RefSpec " + spec //$NON-NLS-1$
          + " already exists."); //$NON-NLS-1$
    specs.add(spec);
    tableViewer.add(spec);
    notifySpecsChanged();
  }

  /**
   * Remove provided specification from this panel. Panel view is
   * automatically refreshed, model is revalidated.
   * <p>
   * Provided specification must be equals with existing one in terms of
   * reference equality, not an equals method.
   * <p>
   * This method should be called only from the UI thread.
   *
   * @param spec
   *            specification to remove.
   * @throws IllegalArgumentException
   *             if specification with this reference doesn't exist in this
   *             panel.
   */
  public void removeRefSpec(final RefSpec spec) {
    final int i = indexOfSpec(spec);
    if (i == -1)
      throw new IllegalArgumentException("RefSpec " + spec //$NON-NLS-1$
          + " not found."); //$NON-NLS-1$
    specs.remove(i);
    tableViewer.remove(spec);
    notifySpecsChanged();
  }

  /**
   * Change some specification to the new one.
   * <p>
   * Old specification must exist in the panel, while new specification can't
   * exist before (both in terms of reference equality).
   * <p>
   * This method should be called only from the UI thread.
   *
   * @param oldSpec
   *            specification to change. Can't be null.
   * @param newSpec
   *            new specification to override existing one. Can't be null.
   */
  public void setRefSpec(final RefSpec oldSpec, final RefSpec newSpec) {
    final int oldI = indexOfSpec(oldSpec);
    if (oldI == -1)
      throw new IllegalArgumentException("RefSpec " + oldSpec //$NON-NLS-1$
          + " not found."); //$NON-NLS-1$
    final int newI = indexOfSpec(newSpec);
    if (newI != -1)
      throw new IllegalArgumentException("RefSpec " + newSpec //$NON-NLS-1$
          + " already exists."); //$NON-NLS-1$
    specs.set(oldI, newSpec);

    // have to refresh whole table as we are operating on immutable objects
    // (this shouldn't be an issue)
    tableViewer.refresh();
    notifySpecsChanged();
  }

  /**
   * Clear all specifications from this panel.
   * <p>
   * This method should be called only from the UI thread.
   */
  public void clearRefSpecs() {
    final RefSpec toRemove[] = specs.toArray(new RefSpec[0]);
    specs.clear();
    tableViewer.remove(toRemove);
    notifySpecsChanged();
  }

  /**
   * Add listener of changes in panel model.
   * <p>
   * Listeners are notified on events caused by both operations invoked by
   * external calls and user interaction. Listener method(s) is always called
   * from UI thread and shouldn't perform long computations.
   * <p>
   * Order of adding listeners is significant. This method is not thread-safe.
   * Listeners should be set up before panel usage.
   *
   * @param listener
   *            listener to add.
   */
  public void addRefSpecTableListener(final SelectionChangeListener listener) {
    listeners.add(listener);
  }

  /**
   * Get user-friendly error message regarding invalid specification.
   *
   * @return user-readable information about invalid specification.
   */
  public String getErrorMessage() {
    return errorMessage;
  }

  /**
   * Return information about validity of specifications.
   * <p>
   * Specifications are considered valid if pushing/fetching (depending on
   * panel configuration) shouldn't cause any error except for
   * non-fast-forward or server-related errors complaint. I.e. specifications
   * destinations don't overlap and every specification is correctly
   * formulated, preferably none is referring to non-existing ref etc.
   *
   * @return true if all specifications in panel are valid, false if at least
   *         one specification is invalid (in this case
   *         {@link #getErrorMessage()} gives detailed information for user).
   */
  public boolean isValid() {
    return errorMessage == null;
  }

  private int indexOfSpec(final RefSpec spec) {
    int i;
    for (i = 0; i < specs.size(); i++) {
      // we have to compare references, not use List#indexOf,
      // as equals is implemented in RefSpec
      if (specs.get(i) == spec)
        break;
    }
    if (i == specs.size())
      return -1;
    return i;
  }

  private void notifySpecsChanged() {
    for (final SelectionChangeListener listener : listeners)
      listener.selectionChanged();
  }

  private void safeCreateResources() {
    imageRegistry.put(IMAGE_ADD, UIIcons.ELCL16_ADD);
    imageRegistry.put(IMAGE_DELETE, UIIcons.ELCL16_DELETE);
    imageRegistry.put(IMAGE_TRASH, UIIcons.ELCL16_TRASH);
    imageRegistry.put(IMAGE_CLEAR, UIIcons.ELCL16_CLEAR);
    errorBackgroundColor = new Color(panel.getDisplay(), 255, 150, 150);
    errorTextColor = new Color(panel.getDisplay(), 255, 0, 0);

    panel.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        imageRegistry.dispose();
        errorBackgroundColor.dispose();
        errorTextColor.dispose();
      }
    });
  }

  private RefContentProposalProvider getRefsProposalProvider(
      final boolean local) {
    return (local ? localProposalProvider : remoteProposalProvider);
  }

  private void createCreationPanel() {
    final Group creationPanel = new Group(panel, SWT.NONE);
    creationPanel.setText(UIText.RefSpecPanel_creationGroup);
    creationPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
        false));
    final GridLayout layout = new GridLayout();
    layout.numColumns = 3;
    layout.horizontalSpacing = 10;
    layout.verticalSpacing = 2;
    creationPanel.setLayout(layout);

    new Label(creationPanel, SWT.NONE)
        .setText(UIText.RefSpecPanel_creationSrc);
    new Label(creationPanel, SWT.NONE)
        .setText(UIText.RefSpecPanel_creationDst);
    creationButton = new Button(creationPanel, SWT.PUSH);
    creationButton.setLayoutData(new GridData(SWT.RIGHT, SWT.BOTTOM, false,
        false, 1, 2));
    creationButton.setImage(imageRegistry.get(IMAGE_ADD));
    creationButton.setText(UIText.RefSpecPanel_creationButton);
    creationButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        final String src = creationSrcComboSupport.getContent();
        final String dst = creationDstComboSupport.getContent();
        RefSpec spec = new RefSpec(src + ':' + dst);
        addRefSpec(spec);
        creationSrcCombo.setText(""); //$NON-NLS-1$
        creationDstCombo.setText(""); //$NON-NLS-1$
      }
    });
    creationButton.setToolTipText(NLS.bind(
        UIText.RefSpecPanel_creationButtonDescription, typeString()));

    creationSrcDecoration = createAssistedDecoratedCombo(creationPanel,
        getRefsProposalProvider(pushSpecs),
        new IContentProposalListener() {
          public void proposalAccepted(IContentProposal proposal) {
            tryAutoCompleteSrcToDst();
          }
        });
    creationSrcCombo = (Combo) creationSrcDecoration.getControl();
    creationSrcCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
        false));
    creationSrcCombo.addTraverseListener(new TraverseListener() {
      public void keyTraversed(TraverseEvent e) {
        // SWT.TRAVERSE_RETURN may be also reasonable here, but
        // it can be confused with RETURN for content proposal
        if (e.detail == SWT.TRAVERSE_TAB_NEXT)
          tryAutoCompleteSrcToDst();
      }
    });
    if (pushSpecs)
      creationSrcCombo
          .setToolTipText(UIText.RefSpecPanel_srcPushDescription);
    else
      creationSrcCombo
          .setToolTipText(UIText.RefSpecPanel_srcFetchDescription);
    creationSrcComboSupport = new ComboLabelingSupport(creationSrcCombo,
        new SelectionAdapter() {
          @Override
          public void widgetSelected(SelectionEvent e) {
            tryAutoCompleteSrcToDst();
          }
        });

    creationDstDecoration = createAssistedDecoratedCombo(creationPanel,
        getRefsProposalProvider(!pushSpecs),
        new IContentProposalListener() {
          public void proposalAccepted(IContentProposal proposal) {
            tryAutoCompleteDstToSrc();
          }
        });
    creationDstCombo = (Combo) creationDstDecoration.getControl();
    creationDstCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
        false));
    creationDstCombo.addTraverseListener(new TraverseListener() {
      public void keyTraversed(TraverseEvent e) {
        // SWT.TRAVERSE_RETURN may be also reasonable here, but
        // it can be confused with RETURN for content proposal
        if (e.detail == SWT.TRAVERSE_TAB_NEXT)
          tryAutoCompleteDstToSrc();
      }
    });
    if (pushSpecs)
      creationDstCombo
          .setToolTipText(UIText.RefSpecPanel_dstPushDescription);
    else
      creationDstCombo
          .setToolTipText(UIText.RefSpecPanel_dstFetchDescription);
    creationDstComboSupport = new ComboLabelingSupport(creationDstCombo,
        new SelectionAdapter() {
          @Override
          public void widgetSelected(SelectionEvent e) {
            tryAutoCompleteDstToSrc();
          }
        });

    validateCreationPanel();
    final ModifyListener validator = new ModifyListener() {
      public void modifyText(final ModifyEvent e) {
        validateCreationPanel();
      }
    };
    creationSrcCombo.addModifyListener(validator);
    creationDstCombo.addModifyListener(validator);
  }

  private void createDeleteCreationPanel() {
    final Group deletePanel = new Group(panel, SWT.NONE);
    deletePanel.setText(UIText.RefSpecPanel_deletionGroup);
    deletePanel
        .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
    final GridLayout layout = new GridLayout();
    layout.numColumns = 3;
    layout.horizontalSpacing = 10;
    deletePanel.setLayout(layout);

    final Label label = new Label(deletePanel, SWT.NONE);
    label.setText(UIText.RefSpecPanel_deletionRef);
    label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

    deleteRefDecoration = createAssistedDecoratedCombo(deletePanel,
        getRefsProposalProvider(false), null);
    deleteRefCombo = (Combo) deleteRefDecoration.getControl();
    deleteRefCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
        false));
    deleteRefCombo
        .setToolTipText(UIText.RefSpecPanel_dstDeletionDescription);
    deleteRefComboSupport = new ComboLabelingSupport(deleteRefCombo, null);

    deleteButton = new Button(deletePanel, SWT.PUSH);
    deleteButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false,
        false));
    deleteButton.setImage(imageRegistry.get(IMAGE_DELETE));
    deleteButton.setText(UIText.RefSpecPanel_deletionButton);
    deleteButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        RefSpec spec = new RefSpec(':' + deleteRefComboSupport.getContent());
        addRefSpec(spec);
        deleteRefCombo.setText(""); //$NON-NLS-1$
      }
    });
    deleteButton
        .setToolTipText(UIText.RefSpecPanel_deletionButtonDescription);
    validateDeleteCreationPanel();

    deleteRefCombo.addModifyListener(new ModifyListener() {
      public void modifyText(final ModifyEvent e) {
        validateDeleteCreationPanel();
      }
    });
  }

  private void createPredefinedCreationPanel() {
    final Group predefinedPanel = new Group(panel, SWT.NONE);
    predefinedPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
        false));
    predefinedPanel.setText(UIText.RefSpecPanel_predefinedGroup);
    final GridLayout layout = new GridLayout();
    layout.numColumns = 3;
    predefinedPanel.setLayout(layout);

    addConfiguredButton = new Button(predefinedPanel, SWT.PUSH);
    addConfiguredButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER,
        true, false));
    addConfiguredButton.setText(NLS.bind(
        UIText.RefSpecPanel_predefinedConfigured, typeStringTitle()));
    addConfiguredButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        addPredefinedRefSpecs(predefinedConfigured);
      }
    });
    addConfiguredButton
        .setToolTipText(UIText.RefSpecPanel_predefinedConfiguredDescription);
    updateAddPredefinedButton(addConfiguredButton, predefinedConfigured);

    addBranchesButton = new Button(predefinedPanel, SWT.PUSH);
    addBranchesButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER,
        true, false));
    addBranchesButton.setText(UIText.RefSpecPanel_predefinedAll);
    addBranchesButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        addPredefinedRefSpecs(predefinedBranches);
      }
    });
    addBranchesButton
        .setToolTipText(UIText.RefSpecPanel_predefinedAllDescription);
    updateAddPredefinedButton(addBranchesButton, predefinedBranches);

    addTagsButton = new Button(predefinedPanel, SWT.PUSH);
    addTagsButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
        false));
    addTagsButton.setText(UIText.RefSpecPanel_predefinedTags);
    addTagsButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        addPredefinedRefSpecs(Transport.REFSPEC_TAGS);
      }
    });
    addTagsButton
        .setToolTipText(UIText.RefSpecPanel_predefinedTagsDescription);
    updateAddPredefinedButton(addTagsButton, Transport.REFSPEC_TAGS);

    addRefSpecTableListener(new SelectionChangeListener() {
      public void selectionChanged() {
        updateAddPredefinedButton(addConfiguredButton,
            predefinedConfigured);
        updateAddPredefinedButton(addBranchesButton, predefinedBranches);
        updateAddPredefinedButton(addTagsButton, Transport.REFSPEC_TAGS);
      }
    });
  }

  private ControlDecoration createAssistedDecoratedCombo(
      final Composite parent,
      final IContentProposalProvider proposalProvider,
      final IContentProposalListener listener) {
    // FIXME: VERY ANNOYING! reported as 243991 in eclipse bugzilla
    // when typing, pressing arrow-down key opens combo box drop-down
    // instead of moving within autocompletion list (Mac 10.4&10.5, Eclipse
    // 3.4)
    final Combo combo = new Combo(parent, SWT.DROP_DOWN);
    final ControlDecoration decoration = new ControlDecoration(combo,
        SWT.BOTTOM | SWT.LEFT);
    final ContentAssistCommandAdapter proposal = new ContentAssistCommandAdapter(
        combo, new ComboContentAdapter(), proposalProvider, null, null,
        true);
    proposal
        .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
    if (listener != null)
      proposal.addContentProposalListener(listener);
    return decoration;
  }

  private void createTableGroup() {
    final Group tableGroup = new Group(panel, SWT.NONE);
    tableGroup.setText(NLS.bind(UIText.RefSpecPanel_specifications,
        typeString()));
    tableGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    tableGroup.setLayout(new GridLayout());

    createTable(tableGroup);
    createSpecsButtonsPanel(tableGroup);
  }

  private void createTable(final Group tableGroup) {
    final Composite tablePanel = new Composite(tableGroup, SWT.NONE);
    final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
    layoutData.heightHint = TABLE_PREFERRED_HEIGHT;
    layoutData.widthHint = TABLE_PREFERRED_WIDTH;
    tablePanel.setLayoutData(layoutData);

    tableViewer = new TableViewer(tablePanel, SWT.FULL_SELECTION
        | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
    ColumnViewerToolTipSupport.enableFor(tableViewer);
    final Table table = tableViewer.getTable();
    table.setLinesVisible(true);
    table.setHeaderVisible(true);

    createTableColumns(tablePanel);
    createCellEditors(table);

    tableViewer.setContentProvider(new ArrayContentProvider());
    tableViewer.setInput(specs);

    tableViewer.setComparer(new IElementComparer() {
      public boolean equals(Object a, Object b) {
        // need that as viewers are not designed to support 2 equals
        // object, while we have RefSpec#equals implemented
        return a == b;
      }

      public int hashCode(Object element) {
        return element.hashCode();
      }
    });
  }

  private void createTableColumns(final Composite tablePanel) {
    final TableColumnLayout columnLayout = new TableColumnLayout();
    tablePanel.setLayout(columnLayout);

    createDummyColumn(columnLayout);
    if (pushSpecs)
      createModeColumn(columnLayout);
    createSrcColumn(columnLayout);
    createDstColumn(columnLayout);
    createForceColumn(columnLayout);
    createRemoveColumn(columnLayout);
  }

  private void createDummyColumn(final TableColumnLayout columnLayout) {
    final TableViewerColumn viewerColumn = new TableViewerColumn(
        tableViewer, SWT.LEFT);
    final TableColumn column = viewerColumn.getColumn();
    columnLayout.setColumnData(column, new ColumnWeightData(0, 0, false));
    viewerColumn.setLabelProvider(new ColumnLabelProvider());
    // FIXME: first cell is left aligned on Mac OS X 10.4, Eclipse 3.4
  }

  private void createModeColumn(final TableColumnLayout columnLayout) {
    final TableViewerColumn column = createColumn(columnLayout,
        UIText.RefSpecPanel_columnMode, COLUMN_MODE_WEIGHT, SWT.CENTER);
    column.setLabelProvider(new ColumnLabelProvider() {
      @Override
      public String getText(final Object element) {
        return (isDeleteRefSpec(element) ? UIText.RefSpecPanel_modeDelete
            : UIText.RefSpecPanel_modeUpdate);
      }

      @Override
      public Image getImage(Object element) {
        return (isDeleteRefSpec(element) ? imageRegistry
            .get(IMAGE_DELETE) : imageRegistry.get(IMAGE_ADD));
      }

      @Override
      public String getToolTipText(Object element) {
        if (isDeleteRefSpec(element))
          return UIText.RefSpecPanel_modeDeleteDescription + '\n'
              + UIText.RefSpecPanel_clickToChange;
        return UIText.RefSpecPanel_modeUpdateDescription + '\n'
            + UIText.RefSpecPanel_clickToChange;
      }
    });
    column.setEditingSupport(new EditingSupport(tableViewer) {
      @Override
      protected boolean canEdit(final Object element) {
        return true;
      }

      @Override
      protected CellEditor getCellEditor(final Object element) {
        return modeCellEditor;
      }

      @SuppressWarnings("boxing")
      @Override
      protected Object getValue(final Object element) {
        return isDeleteRefSpec(element);
      }

      @SuppressWarnings("boxing")
      @Override
      protected void setValue(final Object element, final Object value) {
        final RefSpec oldSpec = (RefSpec) element;
        final RefSpec newSpec;
        if ((Boolean) value) {
          newSpec = setRefSpecSource(oldSpec, null);
          setRefSpec(oldSpec, newSpec);
        } else {
          newSpec = setRefSpecSource(oldSpec,
              UIText.RefSpecPanel_refChooseSome);
          setRefSpec(oldSpec, newSpec);
          tableViewer.getControl().getDisplay().asyncExec(
              new Runnable() {
                public void run() {
                  tableViewer.editElement(newSpec,
                      srcColumnIndex);
                }
              });
        }
      }
    });
  }

  private void createSrcColumn(final TableColumnLayout columnLayout) {
    final TableViewerColumn column = createColumn(columnLayout,
        UIText.RefSpecPanel_columnSrc, COLUMN_SRC_WEIGHT, SWT.LEFT);
    column.setLabelProvider(new ColumnLabelProvider() {
      @Override
      public String getText(final Object element) {
        return ((RefSpec) element).getSource();
      }

      @Override
      public String getToolTipText(Object element) {
        if (isInvalidSpec(element))
          return errorMessage;
        if (isDeleteRefSpec(element))
          return UIText.RefSpecPanel_srcDeleteDescription;
        if (pushSpecs)
          return UIText.RefSpecPanel_srcPushDescription;
        return UIText.RefSpecPanel_srcFetchDescription;
      }

      @Override
      public Color getBackground(final Object element) {
        if (isInvalidSpec(element))
          return errorBackgroundColor;
        return null;
      }

      @Override
      public Color getToolTipForegroundColor(Object element) {
        if (isInvalidSpec(element))
          return errorTextColor;
        return null;
      }
    });
    column.setEditingSupport(new EditingSupport(tableViewer) {
      @Override
      protected boolean canEdit(final Object element) {
        return !isDeleteRefSpec(element);
      }

      @Override
      protected CellEditor getCellEditor(final Object element) {
        return (pushSpecs ? localRefCellEditor : remoteRefCellEditor);
      }

      @Override
      protected Object getValue(final Object element) {
        return ((RefSpec) element).getSource();
      }

      @Override
      protected void setValue(final Object element, final Object value) {
        if (value == null || ((String) value).length() == 0
            || ObjectId.zeroId().name().equals(value)) {
          // Ignore empty strings or null objects - do not set them in
          // model.User won't loose any information if we just fall
          // back to the old value.
          // If user want to delete ref, let change the mode.
          return;
        }

        final RefSpec oldSpec = (RefSpec) element;
        final RefSpec newSpec = setRefSpecSource(oldSpec,
            (String) value);
        setRefSpec(oldSpec, newSpec);
      }
    });

    // find index of this column - for later usage
    final TableColumn[] columns = tableViewer.getTable().getColumns();
    for (srcColumnIndex = 0; srcColumnIndex < columns.length; srcColumnIndex++)
      if (columns[srcColumnIndex] == column.getColumn())
        break;

  }

  private void createDstColumn(final TableColumnLayout columnLayout) {
    final TableViewerColumn column = createColumn(columnLayout,
        UIText.RefSpecPanel_columnDst, COLUMN_DST_WEIGHT, SWT.LEFT);
    column.setLabelProvider(new ColumnLabelProvider() {
      @Override
      public String getText(final Object element) {
        return ((RefSpec) element).getDestination();
      }

      @Override
      public String getToolTipText(Object element) {
        if (isInvalidSpec(element))
          return errorMessage;
        if (isDeleteRefSpec(element))
          return UIText.RefSpecPanel_dstDeletionDescription;
        if (pushSpecs)
          return UIText.RefSpecPanel_dstPushDescription;
        return UIText.RefSpecPanel_dstFetchDescription;
      }

      @Override
      public Color getBackground(final Object element) {
        if (isInvalidSpec(element))
          return errorBackgroundColor;
        return null;
      }

      @Override
      public Color getToolTipForegroundColor(Object element) {
        if (isInvalidSpec(element))
          return errorTextColor;
        return null;
      }
    });
    column.setEditingSupport(new EditingSupport(tableViewer) {
      @Override
      protected boolean canEdit(final Object element) {
        return true;
      }

      @Override
      protected CellEditor getCellEditor(final Object element) {
        return (pushSpecs ? remoteRefCellEditor : localRefCellEditor);
      }

      @Override
      protected Object getValue(final Object element) {
        return ((RefSpec) element).getDestination();
      }

      @Override
      protected void setValue(final Object element, final Object value) {
        if (value == null || ((String) value).length() == 0) {
          // Ignore empty strings - do not set them in model.
          // User won't loose any information if we just fall back
          // to the old value.
          return;
        }

        final RefSpec oldSpec = (RefSpec) element;
        final RefSpec newSpec = setRefSpecDestination(oldSpec,
            (String) value);
        setRefSpec(oldSpec, newSpec);
      }
    });
  }

  private void createForceColumn(final TableColumnLayout columnLayout) {
    final TableViewerColumn column = createColumn(columnLayout,
        UIText.RefSpecPanel_columnForce, COLUMN_FORCE_WEIGHT,
        SWT.CENTER);
    column.setLabelProvider(new CheckboxLabelProvider(tableViewer
        .getControl()) {
      @Override
      protected boolean isChecked(final Object element) {
        return ((RefSpec) element).isForceUpdate();
      }

      @Override
      protected boolean isEnabled(Object element) {
        return !isDeleteRefSpec(element);
      }

      @Override
      public String getToolTipText(Object element) {
        if (!isEnabled(element))
          return UIText.RefSpecPanel_forceDeleteDescription;
        if (isChecked(element))
          return UIText.RefSpecPanel_forceTrueDescription + '\n'
              + UIText.RefSpecPanel_clickToChange;
        return UIText.RefSpecPanel_forceFalseDescription + '\n'
            + UIText.RefSpecPanel_clickToChange;
      }
    });
    column.setEditingSupport(new EditingSupport(tableViewer) {
      @Override
      protected boolean canEdit(final Object element) {
        return !isDeleteRefSpec(element);
      }

      @Override
      protected CellEditor getCellEditor(final Object element) {
        return forceUpdateCellEditor;
      }

      @SuppressWarnings("boxing")
      @Override
      protected Object getValue(final Object element) {
        return ((RefSpec) element).isForceUpdate();
      }

      @SuppressWarnings("boxing")
      @Override
      protected void setValue(final Object element, final Object value) {
        final RefSpec oldSpec = (RefSpec) element;
        final RefSpec newSpec = oldSpec.setForceUpdate((Boolean) value);
        setRefSpec(oldSpec, newSpec);
      }
    });
  }

  private void createRemoveColumn(TableColumnLayout columnLayout) {
    final TableViewerColumn column = createColumn(columnLayout,
        UIText.RefSpecPanel_columnRemove, COLUMN_REMOVE_WEIGHT,
        SWT.CENTER);
    column.setLabelProvider(new CenteredImageLabelProvider() {
      @Override
      public Image getImage(Object element) {
        return imageRegistry.get(IMAGE_TRASH);
      }

      @Override
      public String getToolTipText(Object element) {
        return NLS.bind(UIText.RefSpecPanel_removeDescription,
            typeString());
      }
    });
    column.setEditingSupport(new EditingSupport(tableViewer) {
      @Override
      protected boolean canEdit(Object element) {
        return true;
      }

      @Override
      protected CellEditor getCellEditor(Object element) {
        return removeSpecCellEditor;
      }

      @Override
      protected Object getValue(Object element) {
        return null;
      }

      @Override
      protected void setValue(Object element, Object value) {
        removeRefSpec((RefSpec) element);
      }
    });
  }

  private TableViewerColumn createColumn(
      final TableColumnLayout columnLayout, final String text,
      final int weight, final int style) {
    final TableViewerColumn viewerColumn = new TableViewerColumn(
        tableViewer, style);
    final TableColumn column = viewerColumn.getColumn();
    column.setText(text);
    columnLayout.setColumnData(column, new ColumnWeightData(weight));
    return viewerColumn;
  }

  private void createCellEditors(final Table table) {
    if (pushSpecs)
      modeCellEditor = new CheckboxCellEditor(table);
    localRefCellEditor = createLocalRefCellEditor(table);
    remoteRefCellEditor = createRemoteRefCellEditor(table);
    forceUpdateCellEditor = new CheckboxCellEditor(table);
    removeSpecCellEditor = new ClickableCellEditor(table);
  }

  private CellEditor createLocalRefCellEditor(final Table table) {
    return createRefCellEditor(table, getRefsProposalProvider(true));
  }

  private CellEditor createRemoteRefCellEditor(final Table table) {
    return createRefCellEditor(table, getRefsProposalProvider(false));
  }

  private CellEditor createRefCellEditor(final Table table,
      final IContentProposalProvider proposalProvider) {
    final CellEditor cellEditor = new TextCellEditor(table);

    final Text text = (Text) cellEditor.getControl();
    final ContentAssistCommandAdapter assist = new ContentAssistCommandAdapter(
        text, new TextContentAdapter(), proposalProvider, null, null,
        true);
    assist
        .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);

    return cellEditor;
  }

  private void createSpecsButtonsPanel(final Composite parent) {
    final Composite specsPanel = new Composite(parent, SWT.NONE);
    specsPanel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true,
        false));
    final RowLayout layout = new RowLayout();
    layout.spacing = 10;
    specsPanel.setLayout(layout);

    forceUpdateAllButton = new Button(specsPanel, SWT.PUSH);
    forceUpdateAllButton.setText(UIText.RefSpecPanel_forceAll);
    forceUpdateAllButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        final List<RefSpec> specsCopy = new ArrayList<RefSpec>(specs);
        for (final RefSpec spec : specsCopy) {
          if (!isDeleteRefSpec(spec))
            setRefSpec(spec, spec.setForceUpdate(true));
        }
      }
    });
    forceUpdateAllButton
        .setToolTipText(UIText.RefSpecPanel_forceAllDescription);
    updateForceUpdateAllButton();

    removeAllSpecButton = new Button(specsPanel, SWT.PUSH);
    removeAllSpecButton.setImage(imageRegistry.get(IMAGE_CLEAR));
    removeAllSpecButton.setText(UIText.RefSpecPanel_removeAll);
    removeAllSpecButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        clearRefSpecs();
      }
    });
    removeAllSpecButton
        .setToolTipText(UIText.RefSpecPanel_removeAllDescription);
    updateRemoveAllSpecButton();

    addRefSpecTableListener(new SelectionChangeListener() {
      public void selectionChanged() {
        updateForceUpdateAllButton();
        updateRemoveAllSpecButton();
      }
    });
  }

  private void tryAutoCompleteSrcToDst() {
    final String src = creationSrcComboSupport.getContent();
    final String dst = creationDstComboSupport.getContent();

    if (src == null || src.length() == 0)
      return;

    if (dst != null && dst.length() > 0) {
      // dst is already there, just fix wildcards if needed
      final String newDst;
      if (RefSpec.isWildcard(src))
        newDst = wildcardSpecComponent(dst);
      else
        newDst = unwildcardSpecComponent(dst, src);
      if (!dst.equals(newDst))
        creationDstCombo.setText(newDst);
      return;
    }

    if (!isValidRefExpression(src)) {
      // no way to be smarter than user here
      return;
    }

    // dst is empty, src is ref or wildcard, so we can rewrite it as user
    // would perhaps
    if (pushSpecs) {
      String newDst = src;
      newDst = deletePrefixes(src,
          Constants.R_TAGS.substring(Constants.R_REFS.length()),
          Constants.R_HEADS.substring(Constants.R_REFS.length()));
      creationDstCombo.setText(newDst);
    } else {
      for (final RefSpec spec : predefinedConfigured) {
        if (spec.matchSource(src)) {
          final String newDst = spec.expandFromSource(src)
              .getDestination();
          creationDstCombo.setText(newDst);
          return;
        }
      }
      if (remoteConfig != null && src.startsWith(Constants.R_HEADS)) {
        final String newDst = Constants.R_REMOTES
            + remoteConfig.getName() + '/'
            + src.substring(Constants.R_HEADS.length());
        creationDstCombo.setText(newDst);
      }
    }
  }

  private String deletePrefixes(String ref, String... prefixes) {
    for (String prefix : prefixes)
      if (ref.startsWith(prefix))
        return ref.substring(prefix.length());

    return ref;
  }

  private void tryAutoCompleteDstToSrc() {
    final String src = creationSrcComboSupport.getContent();
    final String dst = creationDstComboSupport.getContent();

    if (dst == null || dst.length() == 0)
      return;

    if (src != null && src.length() > 0) {
      // src is already there, fix wildcards if needed
      final String newSrc;
      if (RefSpec.isWildcard(dst))
        newSrc = wildcardSpecComponent(src);
      else
        newSrc = unwildcardSpecComponent(src, dst);
      if (!src.equals(newSrc))
        creationSrcCombo.setText(newSrc);
      return;
    }
  }

  private void validateCreationPanel() {
    final String src = creationSrcComboSupport.getContent();
    final String dst = creationDstComboSupport.getContent();

    // check src ref field
    boolean srcOk = false;
    final boolean srcWildcard = RefSpec.isWildcard(src);
    if (src == null || src.length() == 0)
      setControlDecoration(creationSrcDecoration,
          FieldDecorationRegistry.DEC_REQUIRED,
          UIText.RefSpecPanel_validationSrcUpdateRequired);
    else if (pushSpecs) {
      if (!srcWildcard && !isLocalRef(src))
        setControlDecoration(creationSrcDecoration,
            FieldDecorationRegistry.DEC_ERROR, NLS.bind(
                UIText.RefSpecPanel_validationRefInvalidLocal,
                src));
      else if (srcWildcard && !isValidRefExpression(src))
        setControlDecoration(
            creationSrcDecoration,
            FieldDecorationRegistry.DEC_ERROR,
            NLS
                .bind(
                    UIText.RefSpecPanel_validationRefInvalidExpression,
                    src));
      else {
        srcOk = true;
        if (srcWildcard && !isMatchingAny(src, localRefNames))
          setControlDecoration(
              creationSrcDecoration,
              FieldDecorationRegistry.DEC_WARNING,
              NLS
                  .bind(
                      UIText.RefSpecPanel_validationRefNonMatchingLocal,
                      src));
        else
          creationSrcDecoration.hide();
      }
    } else {
      if (!srcWildcard && !isRemoteRef(src))
        setControlDecoration(
            creationSrcDecoration,
            FieldDecorationRegistry.DEC_ERROR,
            NLS
                .bind(
                    UIText.RefSpecPanel_validationRefNonExistingRemote,
                    src));
      else if (srcWildcard && !isMatchingAny(src, remoteRefNames)) {
        setControlDecoration(
            creationSrcDecoration,
            FieldDecorationRegistry.DEC_WARNING,
            NLS
                .bind(
                    UIText.RefSpecPanel_validationRefNonMatchingRemote,
                    src));
        srcOk = true;
      } else {
        srcOk = true;
        creationSrcDecoration.hide();
      }
    }

    // check dst ref field
    boolean dstOk = false;
    if (dst == null || dst.length() == 0)
      setControlDecoration(creationDstDecoration,
          FieldDecorationRegistry.DEC_REQUIRED,
          UIText.RefSpecPanel_validationDstRequired);
    else if (!isValidRefExpression(dst))
      setControlDecoration(creationDstDecoration,
          FieldDecorationRegistry.DEC_ERROR, NLS.bind(
              UIText.RefSpecPanel_validationDstInvalidExpression,
              dst));
    else {
      creationDstDecoration.hide();
      dstOk = true;
    }
    // leave duplicates dst checking for validateSpecs()

    // check the wildcard synergy
    boolean wildcardOk = true;
    if (srcOk && dstOk && (srcWildcard ^ RefSpec.isWildcard(dst))) {
      setControlDecoration(creationSrcDecoration,
          FieldDecorationRegistry.DEC_ERROR,
          UIText.RefSpecPanel_validationWildcardInconsistent);
      setControlDecoration(creationDstDecoration,
          FieldDecorationRegistry.DEC_ERROR,
          UIText.RefSpecPanel_validationWildcardInconsistent);
      wildcardOk = false;
    }

    creationButton.setEnabled(srcOk && dstOk && wildcardOk);
  }

  private void validateDeleteCreationPanel() {
    final String ref = deleteRefComboSupport.getContent();

    deleteButton.setEnabled(false);
    if (ref == null || ref.length() == 0)
      setControlDecoration(deleteRefDecoration,
          FieldDecorationRegistry.DEC_REQUIRED,
          UIText.RefSpecPanel_validationRefDeleteRequired);
    else if (!isValidRefExpression(ref))
      setControlDecoration(deleteRefDecoration,
          FieldDecorationRegistry.DEC_ERROR, NLS.bind(
              UIText.RefSpecPanel_validationRefInvalidExpression,
              ref));
    else if (RefSpec.isWildcard(ref))
      setControlDecoration(deleteRefDecoration,
          FieldDecorationRegistry.DEC_ERROR,
          UIText.RefSpecPanel_validationRefDeleteWildcard);
    else if (!isRemoteRef(ref))
      setControlDecoration(
          deleteRefDecoration,
          FieldDecorationRegistry.DEC_ERROR,
          NLS
              .bind(
                  UIText.RefSpecPanel_validationRefNonExistingRemoteDelete,
                  ref));
    else {
      deleteRefDecoration.hide();
      deleteButton.setEnabled(true);
    }
  }

  private void validateSpecs() {
    // validate spec; display max. 1 error message for user at time
    final RefSpec oldInvalidSpec = invalidSpec;
    final RefSpec oldInvalidSpecSameDst = invalidSpecSameDst;
    errorMessage = null;
    invalidSpec = null;
    invalidSpecSameDst = null;
    for (final RefSpec spec : specs) {
      errorMessage = validateSpec(spec);
      if (errorMessage != null) {
        invalidSpec = spec;
        break;
      }
    }
    if (errorMessage == null)
      validateSpecsCrossDst();
    if (invalidSpec != oldInvalidSpec
        || invalidSpecSameDst != oldInvalidSpecSameDst)
      tableViewer.refresh();
  }

  private String validateSpec(final RefSpec spec) {
    final String src = spec.getSource();
    final String dst = spec.getDestination();
    final boolean wildcard = spec.isWildcard();

    // check src
    if (pushSpecs) {
      if (!isDeleteRefSpec(spec)) {
        if (src.length() == 0)
          return UIText.RefSpecPanel_validationSrcUpdateRequired;
        else if (!wildcard && !isLocalRef(src))
          return NLS.bind(
              UIText.RefSpecPanel_validationRefInvalidLocal, src);
        else if (wildcard && !isValidRefExpression(src))
          return NLS.bind(
              UIText.RefSpecPanel_validationRefInvalidExpression,
              src);
        // ignore non-matching wildcard specs
      }
    } else {
      if (src == null || src.length() == 0)
        return UIText.RefSpecPanel_validationSrcUpdateRequired;
      else if (!wildcard && !isRemoteRef(src))
        return NLS
            .bind(
                UIText.RefSpecPanel_validationRefNonExistingRemote,
                src);
      // ignore non-matching wildcard specs
    }

    // check dst
    if (dst == null || dst.length() == 0) {
      if (isDeleteRefSpec(spec))
        return UIText.RefSpecPanel_validationRefDeleteRequired;
      return UIText.RefSpecPanel_validationDstRequired;
    } else if (!isValidRefExpression(dst))
      return NLS.bind(UIText.RefSpecPanel_validationRefInvalidExpression,
          dst);
    else if (isDeleteRefSpec(spec) && !isRemoteRef(dst))
      return NLS.bind(
          UIText.RefSpecPanel_validationRefNonExistingRemoteDelete,
          dst);

    return null;
  }

  private boolean isInvalidSpec(Object element) {
    return element == invalidSpec || element == invalidSpecSameDst;
  }

  private void validateSpecsCrossDst() {
    final Map<String, RefSpec> dstsSpecsMap = new HashMap<String, RefSpec>();
    try {
      for (final RefSpec spec : specs) {
        if (!spec.isWildcard()) {
          if (!tryAddDestination(dstsSpecsMap, spec.getDestination(),
              spec))
            return;
        } else {
          final Collection<String> srcNames;
          if (pushSpecs)
            srcNames = localRefNames;
          else
            srcNames = remoteRefNames;

          for (final String src : srcNames) {
            if (spec.matchSource(src)) {
              final String dst = spec.expandFromSource(src)
                  .getDestination();
              if (!tryAddDestination(dstsSpecsMap, dst, spec))
                return;
            }
          }
        }
      }
    } finally {
      matchingAnyRefs = !dstsSpecsMap.isEmpty();
    }
  }

  private boolean tryAddDestination(final Map<String, RefSpec> dstsSpecsMap,
      final String dst, final RefSpec spec) {
    final RefSpec other = dstsSpecsMap.put(dst, spec);
    if (other != null) {
      errorMessage = NLS
          .bind(
              UIText.RefSpecPanel_validationSpecificationsOverlappingDestination,
              dst);
      invalidSpec = other;
      invalidSpecSameDst = spec;
      return false;
    }
    return true;
  }

  private void updateAddPredefinedButton(final Button button,
      final List<RefSpec> predefined) {
    boolean enable = false;
    if (predefined != null) {
      for (final RefSpec pre : predefined) {
        if (!specs.contains(pre)) {
          enable = true;
          break;
        }
      }
    }
    button.setEnabled(enable);
  }

  private void updateAddPredefinedButton(Button button,
      final RefSpec predefined) {
    button.setEnabled(!specs.contains(predefined));
  }

  private void updateForceUpdateAllButton() {
    boolean enable = false;
    for (final RefSpec spec : specs) {
      if (!isDeleteRefSpec(spec) && !spec.isForceUpdate()) {
        enable = true;
        break;
      }
    }
    forceUpdateAllButton.setEnabled(enable);
  }

  private void updateRemoveAllSpecButton() {
    removeAllSpecButton.setEnabled(!specs.isEmpty());
  }

  private String typeString() {
    return (pushSpecs ? UIText.RefSpecPanel_push
        : UIText.RefSpecPanel_fetch);
  }

  private String typeStringTitle() {
    return (pushSpecs ? UIText.RefSpecPanel_pushTitle
        : UIText.RefSpecPanel_fetchTitle);
  }

  private void addPredefinedRefSpecs(final RefSpec predefined) {
    addPredefinedRefSpecs(Collections.singletonList(predefined));
  }

  private void addPredefinedRefSpecs(final List<RefSpec> predefined) {
    for (final RefSpec pre : predefined) {
      if (!specs.contains(pre))
        addRefSpec(pre);
    }
  }

  private List<RefContentProposal> createContentProposals(
      final Collection<Ref> refs, final Ref HEAD) {
    final TreeSet<Ref> set = new TreeSet<Ref>(new Comparator<Ref>() {
      public int compare(Ref o1, Ref o2) {
        // lexicographical ordering by name seems to be fine
        return o1.getName().compareTo(o2.getName());
      }
    });
    set.addAll(refs);
    if (HEAD != null)
      set.add(HEAD);

    final List<RefContentProposal> result = new ArrayList<RefContentProposal>(
        set.size());
    for (final Ref r : set)
      result.add(new RefContentProposal(localDb, r));
    return result;
  }

  private List<RefContentProposal> createProposalsFilteredLocal(
      final List<RefContentProposal> proposals) {
    final List<RefContentProposal> result = new ArrayList<RefContentProposal>();
    for (final RefContentProposal p : proposals) {
      final String content = p.getContent();
      if (pushSpecs) {
        if (content.equals(Constants.HEAD)
            || content.startsWith(Constants.R_HEADS))
          result.add(p);
      } else {
        if (content.startsWith(Constants.R_REMOTES))
          result.add(p);
      }
    }
    return result;
  }

  private boolean isRemoteRef(String ref) {
    return remoteRefNames.contains(ref);
  }

  private boolean isLocalRef(final String ref) {
    return tryResolveLocalRef(ref) != null;
  }

  private boolean isMatchingAny(final String ref,
      final Collection<String> names) {
    // strip wildcard sign
    final String prefix = ref.substring(0, ref.length() - 1);
    for (final String name : names)
      if (name.startsWith(prefix))
        return true;
    return false;
  }

  private ObjectId tryResolveLocalRef(final String ref) {
    if (!isValidRefExpression(ref))
      return null;
    try {
      return localDb.resolve(ref);
    } catch (final IOException e) {
      Activator.logError(
          "I/O error occurred during resolving expression: " //$NON-NLS-1$
              + ref, e);
      return null;
    }
  }

  private class RefContentProposalProvider implements
      IContentProposalProvider {
    private List<RefContentProposal> proposals = Collections.emptyList();

    private final boolean tryResolvingLocally;

    private RefContentProposalProvider(final boolean tryResolvingLocally) {
      this.tryResolvingLocally = tryResolvingLocally;
    }

    private void setProposals(final List<RefContentProposal> proposals) {
      this.proposals = proposals;
    }

    public IContentProposal[] getProposals(final String contents,
        int position) {
      final List<RefContentProposal> result = new ArrayList<RefContentProposal>();

      if (contents.indexOf('*') != -1 || contents.indexOf('?') != -1) {
        // contents contains wildcards

        // check if contents can be safely added as wildcard spec
        if (isValidRefExpression(contents))
          result.add(new RefContentProposal(localDb, contents, null));

        // let's expand wildcards
        final String regex = ".*" //$NON-NLS-1$
            + contents.replace("*", ".*").replace("?", ".?") + ".*"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
        final Pattern p = Pattern.compile(regex);
        for (final RefContentProposal prop : proposals)
          if (p.matcher(prop.getContent()).matches())
            result.add(prop);
      } else {
        for (final RefContentProposal prop : proposals)
          if (prop.getContent().contains(contents))
            result.add(prop);

        if (tryResolvingLocally && result.isEmpty()) {
          final ObjectId id = tryResolveLocalRef(contents);
          if (id != null)
            result
                .add(new RefContentProposal(localDb, contents,
                    id));
        }
      }
      return result.toArray(new IContentProposal[0]);
    }
  }
}
TOP

Related Classes of org.eclipse.egit.ui.internal.components.RefSpecPanel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.