[568293] Make CDO Explorer's "Show In" sub menu extensible

https://bugs.eclipse.org/bugs/show_bug.cgi?id=568293
diff --git a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/actions/ShowInActionProvider.java b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/actions/ShowInActionProvider.java
index 45b6994..971d7a1 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/actions/ShowInActionProvider.java
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/actions/ShowInActionProvider.java
@@ -41,11 +41,13 @@
 import org.eclipse.net4j.util.container.IManagedContainer;
 import org.eclipse.net4j.util.container.IPluginContainer;
 import org.eclipse.net4j.util.io.IOUtil;
+import org.eclipse.net4j.util.ui.MenuFiller;
 
 import org.eclipse.emf.ecore.EObject;
 
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.Separator;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.StructuredViewer;
 import org.eclipse.jface.viewers.TreeViewer;
@@ -83,7 +85,7 @@
 
   public static final String HISTORY_VIEW_ID = "org.eclipse.team.ui.GenericHistoryView";
 
-  private static final String ID = ShowInActionProvider.class.getName();
+  public static final String ID = ShowInActionProvider.class.getName();
 
   private static final boolean PROPERTIES_SUPPORT_AVAILABLE = Support.PROPERTIES.isAvailable();
 
@@ -236,7 +238,13 @@
       filled = true;
     }
 
-    return filled;
+    menu.add(new Separator(ICommonMenuConstants.GROUP_ADDITIONS));
+
+    boolean[] finalFilled = { filled };
+    IPluginContainer.INSTANCE.forEachElement(MenuFiller.Factory.PRODUCT_GROUP, MenuFiller.class,
+        filler -> finalFilled[0] |= filler.fillMenu(page, viewer, menu, selectedElement));
+
+    return finalFilled[0];
   }
 
   private static boolean addAction(IMenuManager subMenu, Object selectedElement, ShowInViewAction action)
@@ -425,7 +433,7 @@
   /**
    * @author Eike Stepper
    */
-  private static class ShowInViewAction extends Action
+  public static class ShowInViewAction extends Action
   {
     private final IWorkbenchPage page;
 
@@ -499,7 +507,7 @@
   /**
    * @author Eike Stepper
    */
-  private static final class ShowInProjectExplorerAction extends ShowInViewAction
+  public static final class ShowInProjectExplorerAction extends ShowInViewAction
   {
     private final CDOCheckout[] checkouts;
 
@@ -523,7 +531,7 @@
   /**
    * @author Eike Stepper
    */
-  private static final class ShowInSessionsViewAction extends ShowInViewAction
+  public static final class ShowInSessionsViewAction extends ShowInViewAction
   {
     private final CDORepository repository;
 
@@ -622,7 +630,7 @@
   /**
    * @author Eike Stepper
    */
-  private static final class ShowInSystemExplorerAction extends Action
+  public static final class ShowInSystemExplorerAction extends Action
   {
     private final File folder;
 
diff --git a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/MenuFiller.java b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/MenuFiller.java
new file mode 100644
index 0000000..0ec23b8
--- /dev/null
+++ b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/MenuFiller.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.net4j.util.ui;
+
+import org.eclipse.net4j.util.factory.ProductCreationException;
+
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.viewers.StructuredViewer;
+import org.eclipse.ui.IWorkbenchPage;
+
+/**
+ * @author Eike Stepper
+ * @since 3.9
+ */
+public interface MenuFiller
+{
+  public boolean fillMenu(IWorkbenchPage page, StructuredViewer viewer, IMenuManager menu, Object selectedElement);
+
+  /**
+   * @author Eike Stepper
+   */
+  public static abstract class Factory extends org.eclipse.net4j.util.factory.Factory
+  {
+    public static final String PRODUCT_GROUP = "org.eclipse.net4j.util.ui.menuFillers";
+
+    public Factory(String type)
+    {
+      super(PRODUCT_GROUP, type);
+    }
+
+    @Override
+    public abstract MenuFiller create(String description) throws ProductCreationException;
+  }
+}
diff --git a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/views/ContainerView.java b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/views/ContainerView.java
index f63347c..f7f3e3a 100644
--- a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/views/ContainerView.java
+++ b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/views/ContainerView.java
@@ -360,25 +360,18 @@
 
   protected void hookDoubleClick()
   {
-    viewer.addDoubleClickListener(event -> {
-      ITreeSelection selection = (ITreeSelection)viewer.getSelection();
-      Object object = selection.getFirstElement();
-      doubleClicked(object);
-    });
+    viewer.addDoubleClickListener(e -> doubleClicked(((ITreeSelection)viewer.getSelection()).getFirstElement()));
   }
 
   protected void hookContextMenu()
   {
-    MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
-    menuMgr.setRemoveAllWhenShown(true);
-    menuMgr.addMenuListener(manager -> {
-      ITreeSelection selection = (ITreeSelection)viewer.getSelection();
-      fillContextMenu(manager, selection);
-    });
+    MenuManager manager = new MenuManager("#PopupMenu"); //$NON-NLS-1$
+    manager.setRemoveAllWhenShown(true);
+    manager.addMenuListener(m -> fillContextMenu(m, (ITreeSelection)viewer.getSelection()));
 
-    Menu menu = menuMgr.createContextMenu(viewer.getControl());
+    Menu menu = manager.createContextMenu(viewer.getControl());
     viewer.getControl().setMenu(menu);
-    getSite().registerContextMenu(menuMgr, viewer);
+    getSite().registerContextMenu(manager, viewer);
   }
 
   protected void contributeToActionBars()
@@ -406,10 +399,28 @@
 
   /**
    * @since 3.5
+   * @deprecated As of 3.9 use {@link #addMenuGroupAdditions(IContributionManager)}.
    */
+  @Deprecated
   protected void addSeparator(IContributionManager manager)
   {
-    manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
+    addMenuGroupAdditions(manager);
+  }
+
+  /**
+   * @since 3.9
+   */
+  protected void addMenuGroupAdditions(IContributionManager manager)
+  {
+    addMenuGroup(manager, IWorkbenchActionConstants.MB_ADDITIONS);
+  }
+
+  /**
+   * @since 3.9
+   */
+  protected void addMenuGroup(IContributionManager manager, String groupName)
+  {
+    manager.add(new Separator(groupName));
   }
 
   /**
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/IManagedContainer.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/IManagedContainer.java
index 18f3300..a7c1d5f 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/IManagedContainer.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/IManagedContainer.java
@@ -21,6 +21,8 @@
 import java.io.OutputStream;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * A {@link IContainer container} that populates itself by means of element {@link #getFactoryRegistry() factories} and
@@ -82,6 +84,21 @@
 
   public Object removeElement(String productGroup, String factoryType, String description);
 
+  /**
+   * @since 3.13
+   */
+  public <T> void forEachElement(String productGroup, Class<T> productType, Function<String, String> descriptionProvider, Consumer<T> consumer);
+
+  /**
+   * @since 3.13
+   */
+  public <T> void forEachElement(String productGroup, Class<T> productType, String description, Consumer<T> consumer);
+
+  /**
+   * @since 3.13
+   */
+  public <T> void forEachElement(String productGroup, Class<T> productType, Consumer<T> consumer);
+
   public void clearElements();
 
   public void loadElements(InputStream stream) throws IOException, FactoryNotFoundException, ProductCreationException;
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/ManagedContainer.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/ManagedContainer.java
index 2dc7b54..ff22a2a 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/ManagedContainer.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/ManagedContainer.java
@@ -44,6 +44,8 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * A default implementation of a {@link IManagedContainer managed container}.
@@ -461,6 +463,42 @@
   }
 
   @Override
+  public <T> void forEachElement(String productGroup, Class<T> productType, Function<String, String> descriptionProvider, Consumer<T> consumer)
+  {
+    for (String type : getFactoryTypes(productGroup))
+    {
+      String description = descriptionProvider == null ? null : descriptionProvider.apply(type);
+
+      try
+      {
+        @SuppressWarnings("unchecked")
+        T element = (T)getElement(productGroup, type, description);
+        consumer.accept(element);
+      }
+      catch (FactoryNotFoundException ex)
+      {
+        // Should not happen.
+      }
+      catch (ProductCreationException ex)
+      {
+        OM.LOG.error(ex);
+      }
+    }
+  }
+
+  @Override
+  public <T> void forEachElement(String productGroup, Class<T> productType, String description, Consumer<T> consumer)
+  {
+    forEachElement(productGroup, productType, type -> description, consumer);
+  }
+
+  @Override
+  public <T> void forEachElement(String productGroup, Class<T> productType, Consumer<T> consumer)
+  {
+    forEachElement(productGroup, productType, type -> null, consumer);
+  }
+
+  @Override
   public void clearElements()
   {
     checkActive();