484260: create commands for build toolbar

Change-Id: I8aba963dd787d6dce61e9585000844a5cc8b8e3d
Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=484260
diff --git a/org.eclipse.mylyn.builds.ui/icons/eview16/web.png b/org.eclipse.mylyn.builds.ui/icons/eview16/web.png
new file mode 100644
index 0000000..94eec2c
--- /dev/null
+++ b/org.eclipse.mylyn.builds.ui/icons/eview16/web.png
Binary files differ
diff --git a/org.eclipse.mylyn.builds.ui/plugin.xml b/org.eclipse.mylyn.builds.ui/plugin.xml
index 0d863b0..041ee39 100644
--- a/org.eclipse.mylyn.builds.ui/plugin.xml
+++ b/org.eclipse.mylyn.builds.ui/plugin.xml
@@ -159,6 +159,21 @@
           id="org.eclipse.mylyn.builds.ui.command.NewTaskFromTest"
           name="New Task From Test">
     </command>
+    <command
+          defaultHandler="org.eclipse.mylyn.internal.builds.ui.commands.BuildUrlCommandHandler$ShowTestResultsUrlHandler"
+          id="org.eclipse.mylyn.builds.ui.command.ShowTestResults.url"
+          name="Show Test Results">
+    </command>
+    <command
+          defaultHandler="org.eclipse.mylyn.internal.builds.ui.commands.BuildUrlCommandHandler$ShowBuildOutputUrlHandler"
+          id="org.eclipse.mylyn.builds.ui.command.ShowBuildOutput.url"
+          name="Show Build Output">
+    </command>
+    <command
+          defaultHandler="org.eclipse.mylyn.internal.builds.ui.commands.BuildUrlCommandHandler$OpenWithBrowserUrlHandler"
+          id="org.eclipse.mylyn.builds.ui.commands.OpenBuildElementWithBrowser.url"
+          name="Open Build with Browser">
+    </command>
  </extension>
  <extension
        point="org.eclipse.ui.commandImages">
@@ -167,10 +182,18 @@
           icon="icons/etool16/console.gif">
     </image>
     <image
+          commandId="org.eclipse.mylyn.builds.ui.command.ShowBuildOutput.url"
+          icon="icons/etool16/console.gif">
+    </image>
+    <image
           commandId="org.eclipse.mylyn.builds.ui.command.ShowTestResults"
           icon="icons/eview16/junit.gif">
     </image>
     <image
+          commandId="org.eclipse.mylyn.builds.ui.command.ShowTestResults.url"
+          icon="icons/eview16/junit.gif">
+    </image>
+    <image
           commandId="org.eclipse.mylyn.builds.ui.command.RunBuild"
           disabledIcon="icons/dtool16/run_exc.gif"
           icon="icons/etool16/run_exc.gif">
@@ -834,4 +857,27 @@
           id="org.eclipse.mylyn.builds.ui.urlHandler.BuildsUrlHandler">
     </handler>
  </extension>
+ <extension
+       point="org.eclipse.ui.menus">
+    <menuContribution
+          allPopups="false"
+          locationURI="toolbar:org.eclipse.mylyn.build.toolbar">
+       <command
+             commandId="org.eclipse.mylyn.builds.ui.command.ShowTestResults.url"
+             label="Show Test Results"
+             style="push">
+       </command>
+       <command
+             commandId="org.eclipse.mylyn.builds.ui.command.ShowBuildOutput.url"
+             label="Show Build Output"
+             style="push">
+       </command>
+       <command
+             commandId="org.eclipse.mylyn.builds.ui.commands.OpenBuildElementWithBrowser.url"
+             icon="icons/eview16/web.png"
+             label="Open Build with Browser"
+             style="push">
+       </command>
+    </menuContribution>
+ </extension>
 </plugin>
diff --git a/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/BuildUrlCommandHandler.java b/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/BuildUrlCommandHandler.java
new file mode 100644
index 0000000..fd74018
--- /dev/null
+++ b/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/BuildUrlCommandHandler.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Tasktop Technologies 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:
+ *     Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.builds.ui.commands;
+
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.mylyn.builds.core.IBuild;
+import org.eclipse.mylyn.builds.core.IBuildPlan;
+import org.eclipse.mylyn.builds.core.IBuildServer;
+import org.eclipse.mylyn.builds.core.spi.BuildServerBehaviour;
+import org.eclipse.mylyn.builds.core.spi.GetBuildsRequest;
+import org.eclipse.mylyn.builds.core.spi.GetBuildsRequest.Scope;
+import org.eclipse.mylyn.builds.internal.core.BuildFactory;
+import org.eclipse.mylyn.builds.ui.BuildsUi;
+import org.eclipse.mylyn.commons.core.StatusHandler;
+import org.eclipse.mylyn.commons.workbench.browser.BrowserUtil;
+import org.eclipse.mylyn.internal.builds.ui.BuildsUiInternal;
+import org.eclipse.mylyn.internal.builds.ui.BuildsUiPlugin;
+import org.eclipse.mylyn.internal.builds.ui.actions.ShowBuildOutputAction;
+import org.eclipse.mylyn.internal.builds.ui.actions.ShowTestResultsAction;
+import org.eclipse.mylyn.internal.builds.ui.view.NewBuildServerAction;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.BaseSelectionListenerAction;
+
+public class BuildUrlCommandHandler extends AbstractHandler {
+	public static class ShowTestResultsUrlHandler extends BuildUrlCommandHandler {
+		public ShowTestResultsUrlHandler() {
+			super(new ShowTestResultsAction());
+		}
+	}
+
+	public static class ShowBuildOutputUrlHandler extends BuildUrlCommandHandler {
+		public ShowBuildOutputUrlHandler() {
+			super(new ShowBuildOutputAction());
+		}
+	}
+
+	public static class OpenWithBrowserUrlHandler extends BuildUrlCommandHandler {
+		public OpenWithBrowserUrlHandler() {
+			super(new BaseSelectionListenerAction(Messages.BuildUrlCommandHandler_Open_with_Browser) {
+				@Override
+				public void run() {
+					Object selection = getStructuredSelection().getFirstElement();
+					if (selection instanceof String) {
+						BrowserUtil.openUrl((String) selection, BrowserUtil.NO_RICH_EDITOR);
+					}
+				}
+			});
+			setNeedsDownload(false);
+		}
+	}
+
+	private static class BuildCache {
+		private WeakReference<IBuild> lastBuild = new WeakReference<IBuild>(null);
+
+		private BuildUrlCommandHandler lastHandler;
+
+		public void put(BuildUrlCommandHandler handler, IBuild build) {
+			lastBuild = new WeakReference<IBuild>(build);
+			lastHandler = handler;
+		}
+
+		public IBuild get(BuildUrlCommandHandler handler, String buildUrl) {
+			if (lastHandler != handler) {
+				// same handler should never get the build back from the cache; clicking same button twice re-downloads
+				IBuild build = lastBuild.get();
+				if (build != null && buildUrl.equals(build.getUrl())) {
+					lastBuild.clear();// can only retrieve build once
+					return build;
+				}
+			}
+			return null;
+		}
+	}
+
+	private static BuildCache buildCache = new BuildCache();
+
+	private final BaseSelectionListenerAction action;
+
+	private boolean needsDownload = true;
+
+	public BuildUrlCommandHandler(BaseSelectionListenerAction action) {
+		this.action = action;
+	}
+
+	protected void setNeedsDownload(boolean needsDownload) {
+		this.needsDownload = needsDownload;
+	}
+
+	@Override
+	public Object execute(ExecutionEvent event) throws ExecutionException {
+		if (event.getTrigger() instanceof Event) {
+			Object data = ((Event) event.getTrigger()).widget.getData();
+			if (data instanceof String) {
+				String buildUrl = (String) data;
+				if (needsDownload) {
+					final IBuildServer buildServer = findServerForBuild(buildUrl);
+					if (buildServer != null) {
+						downloadBuildAndRunAction(buildServer, buildUrl);
+					} else {
+						new NewBuildServerAction().run();
+					}
+				} else {
+					action.selectionChanged(new StructuredSelection(buildUrl));
+					action.run();
+				}
+			}
+		}
+		return null;
+	}
+
+	private IBuildServer findServerForBuild(String buildUrl) {
+		for (IBuildServer server : BuildsUiInternal.getModel().getServers()) {
+			if (buildUrl.startsWith(server.getUrl())) {
+				return server;
+			}
+		}
+		return null;
+	}
+
+	protected void downloadBuildAndRunAction(final IBuildServer buildServer, final String buildUrl) {
+		IBuild build = buildCache.get(this, buildUrl);
+		if (build != null) {
+			action.selectionChanged(new StructuredSelection(build));
+			action.run();
+			return;
+		}
+		Job job = new Job(NLS.bind(Messages.BuildUrlCommandHandler_Downloading_Build_X, buildUrl)) {
+
+			@Override
+			protected IStatus run(IProgressMonitor monitor) {
+				try {
+					BuildServerBehaviour behaviour = BuildsUi.getConnector(buildServer.getConnectorKind())
+							.getBehaviour(buildServer.getLocation());
+					String buildUrl2 = removeEnd(buildUrl, "/"); //$NON-NLS-1$
+					String buildId = substringAfterLast(buildUrl2, "/"); //$NON-NLS-1$
+					String planId = substringAfterLast(substringBeforeLast(buildUrl2, "/"), "/"); //$NON-NLS-1$ //$NON-NLS-2$
+					IBuildPlan plan = BuildFactory.eINSTANCE.createBuildPlan();
+					plan.setId(planId);
+					plan.setName(planId);
+					GetBuildsRequest request = new GetBuildsRequest(plan, Collections.singletonList(buildId),
+							Scope.FULL);
+					@SuppressWarnings("restriction")
+					final List<IBuild> builds = behaviour.getBuilds(request,
+							new org.eclipse.mylyn.internal.commons.core.operations.NullOperationMonitor());
+					if (!builds.isEmpty()) {
+						IBuild populatedBuild = builds.get(0);
+						populatedBuild.setServer(buildServer);
+						populatedBuild.setPlan(plan);
+						buildCache.put(BuildUrlCommandHandler.this, populatedBuild);
+						action.selectionChanged(new StructuredSelection(populatedBuild));
+						PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
+							public void run() {
+								action.run();
+							}
+						});
+					}
+				} catch (CoreException e) {
+					StatusHandler.log(new Status(IStatus.ERROR, BuildsUiPlugin.ID_PLUGIN, e.getMessage(), e));
+				}
+				return Status.OK_STATUS;
+			}
+		};
+		job.setUser(true);
+		job.schedule();
+	}
+
+	protected String substringBeforeLast(String s, String last) {
+		int i = s.lastIndexOf(last);
+		if (i != -1) {
+			return s.substring(0, i);
+		}
+		return s;
+	}
+
+	private String substringAfterLast(String s, String last) {
+		int i = s.lastIndexOf(last);
+		if (i != -1) {
+			return s.substring(i + last.length());
+		}
+		return s;
+	}
+
+	private String removeEnd(final String s, String end) {
+		if (s.endsWith(end)) {
+			return s.substring(0, s.length() - end.length());
+		}
+		return s;
+	}
+}
diff --git a/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/Messages.java b/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/Messages.java
new file mode 100644
index 0000000..a537842
--- /dev/null
+++ b/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/Messages.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Tasktop Technologies 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:
+ *     Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.builds.ui.commands;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+	private static final String BUNDLE_NAME = "org.eclipse.mylyn.internal.builds.ui.commands.messages"; //$NON-NLS-1$
+
+	public static String BuildUrlCommandHandler_Downloading_Build_X;
+
+	public static String BuildUrlCommandHandler_Open_with_Browser;
+	static {
+		// initialize resource bundle
+		NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+	}
+
+	private Messages() {
+	}
+}
diff --git a/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/messages.properties b/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/messages.properties
new file mode 100644
index 0000000..b487eec
--- /dev/null
+++ b/org.eclipse.mylyn.builds.ui/src/org/eclipse/mylyn/internal/builds/ui/commands/messages.properties
@@ -0,0 +1,2 @@
+BuildUrlCommandHandler_Downloading_Build_X=Downloading Build {0}
+BuildUrlCommandHandler_Open_with_Browser=Open with Browser