com.cloudbees.eclipse.dev.ui.views.jobs.JobsView.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudbees.eclipse.dev.ui.views.jobs.JobsView.java

Source

/*******************************************************************************
 * Copyright (c) 2013 Cloud Bees, Inc.
 * All rights reserved. 
 * This program is 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:
 *    Cloud Bees, Inc. - initial API and implementation 
 *******************************************************************************/
package com.cloudbees.eclipse.dev.ui.views.jobs;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.PendingUpdateAdapter;

import com.cloudbees.eclipse.core.CBRemoteChangeAdapter;
import com.cloudbees.eclipse.core.CBRemoteChangeListener;
import com.cloudbees.eclipse.core.CloudBeesException;
import com.cloudbees.eclipse.core.JenkinsService;
import com.cloudbees.eclipse.core.jenkins.api.JenkinsBuild;
import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse;
import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse.Job;
import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse.JobViewGeneric;
import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse.View;
import com.cloudbees.eclipse.core.util.Utils;
import com.cloudbees.eclipse.dev.ui.CBDEVImages;
import com.cloudbees.eclipse.dev.ui.CloudBeesDevUiPlugin;
import com.cloudbees.eclipse.dev.ui.actions.DeleteJobAction;
import com.cloudbees.eclipse.dev.ui.actions.InvokeBuildAction;
import com.cloudbees.eclipse.dev.ui.actions.OpenBuildAction;
import com.cloudbees.eclipse.dev.ui.actions.OpenLogAction;
import com.cloudbees.eclipse.dev.ui.actions.ReloadBuildHistoryAction;
import com.cloudbees.eclipse.dev.ui.actions.ReloadJobsAction;
import com.cloudbees.eclipse.ui.CloudBeesUIPlugin;
import com.cloudbees.eclipse.ui.PreferenceConstants;

/**
 * View showing jobs for both Jenkins on-premise installations and DEV@cloud jenkins instances
 * 
 * @author ahtik
 */
public class JobsView extends ViewPart implements IPropertyChangeListener {

    public static final String ID = "com.cloudbees.eclipse.dev.ui.views.jobs.JobsView";

    private TreeViewer treeViewer;

    protected ReloadJobsAction actionReloadJobs;
    private InvokeBuildAction actionInvokeBuild;
    private Action actionOpenJobInBrowser;

    private OpenBuildAction actionOpenLastBuildDetails;
    private ReloadBuildHistoryAction actionOpenBuildHistory;
    private OpenLogAction actionOpenLog;
    private Action actionDeleteJob;

    private final Map<String, Image> stateIcons = new HashMap<String, Image>();

    private CBRemoteChangeListener jenkinsChangeListener;

    private JobsContentProvider contentProvider;

    protected Runnable regularRefresher;

    private String viewUrl;

    protected JobHolder selectedJobHolder;

    public JobsView() {
        super();
    }

    public JobHolder getSelectedJob() {
        return this.selectedJobHolder;
    }

    public void setSelectedJob(final JobHolder job) {
        this.selectedJobHolder = job;
        boolean enable = this.selectedJobHolder != null;
        this.actionInvokeBuild.setJob(this.selectedJobHolder);
        this.actionOpenLastBuildDetails.setBuild(
                getJob(this.selectedJobHolder) instanceof Job ? ((Job) (getJob(this.selectedJobHolder))).lastBuild
                        : null);
        this.actionOpenLog.setBuild(
                getJob(this.selectedJobHolder) instanceof Job ? ((Job) getJob(this.selectedJobHolder)).lastBuild
                        : null);
        this.actionDeleteJob.setEnabled(getJob(this.selectedJobHolder) instanceof Job
                && ((Job) getJob(this.selectedJobHolder)).color != null);
        this.actionOpenJobInBrowser.setEnabled(enable);
        this.actionOpenBuildHistory.setViewUrl(getJob(this.selectedJobHolder) instanceof Job
                && ((Job) getJob(this.selectedJobHolder)).color != null ? ((Job) getJob(this.selectedJobHolder)).url
                        : null);

        //FIXME color!=null is currently the only known way to know if this job is not a folder. 
        this.actionOpenBuildHistory.setEnabled(getJob(this.selectedJobHolder) instanceof Job
                && ((Job) getJob(this.selectedJobHolder)).color != null);

    }

    /**
     * Helper method to return the JobViewGeneric from the holder which is expected to be instanceof JobHolder. Param is
     * Object to provider easier usage.
     * 
     * @param holder
     * @return
     */
    private JobViewGeneric getJob(Object holder) {
        if (holder == null)
            return null;
        if (!(holder instanceof JobHolder)) {
            return null;//throw new RuntimeException("Unexpected holder type! Expected JobHelper, got "+holder);
        }

        return ((JobHolder) holder).job;

    }

    public ReloadJobsAction getReloadJobsAction() {
        return this.actionReloadJobs;
    }

    protected void setInput(final JenkinsJobsResponse newView) {

        if (!isCurrentView(newView.viewUrl)) {
            return; // another view
        }

        if (newView == null || (newView.jobs == null && newView.views == null)) {
            String post = "";
            if (newView.name != null && newView.name.length() > 0) {
                post = " for " + newView.name;
            }
            setContentDescription(" No jobs available" + post);
            this.contentProvider.inputChanged(this.treeViewer, null, new ArrayList<JobHolder>());
        } else {
            JenkinsService ss = CloudBeesUIPlugin.getDefault().getJenkinsServiceForUrl(newView.viewUrl);
            String label = ss.getLabel();

            String viewInfo = "";
            String post = "";
            if (newView.name != null && newView.name.length() > 0 && label != null && !newView.name.equals(label)) {
                viewInfo = newView.name + " [";
                post = "#" + newView.name;
            }
            setContentDescription(
                    " " + viewInfo + label + (viewInfo.length() > 0 ? "]" : "") + " (" + new Date() + ")");
            setPartName("Build Jobs [" + label + post + "]");

            List<JobHolder> reslist = new ArrayList<JobHolder>();

            // Also add views if it's not the main url
            if (!newView.viewUrl.equals(ss.getUrl() + "/") && newView.views != null) {

                for (View view : newView.views) {
                    if (view.url != null
                            && (newView.primaryView == null || !view.url.equals(newView.primaryView.url))) {
                        reslist.add(new JobHolder(view, null));
                    }
                }

            }

            if (newView.jobs != null) {
                for (Job job : newView.jobs) {
                    reslist.add(new JobHolder(job, null));
                }

            }
            this.contentProvider.inputChanged(this.treeViewer, null, reslist);

        }

        if (newView != null) {
            this.viewUrl = newView.viewUrl;
        }
        this.actionReloadJobs.viewUrl = this.viewUrl;
        this.actionReloadJobs.treeViewer = this.treeViewer;

        // Preserve the expended state and refresh
        //TreePath[] paths = treeViewer.getExpandedTreePaths();
        this.treeViewer.refresh();
        //treeViewer.setExpandedTreePaths(paths);

        boolean reloadable = newView != null;
        this.actionReloadJobs.setEnabled(reloadable);

        if (reloadable) {
            startRefresher();
        } else {
            stopRefresher();
        }
    }

    private boolean isCurrentView(String viewUrl) {
        if (viewUrl != null) {
            IViewSite site = getViewSite();
            String secId = site.getSecondaryId();
            String servUrl = CloudBeesUIPlugin.getDefault().getJenkinsServiceForUrl(viewUrl).getUrl();
            if (secId != null && servUrl != null && !secId.equals(Long.toString(servUrl.hashCode()))) {
                return false; // another view
            }
        }
        return true;
    }

    protected synchronized void stopRefresher() {
        this.regularRefresher = null;
    }

    protected synchronized void startRefresher() {
        if (this.regularRefresher != null) {
            return; // already running
        }

        if (this.viewUrl == null) {
            return; // nothing to refresh anyway
        }

        boolean enabled = CloudBeesUIPlugin.getDefault().getPreferenceStore()
                .getBoolean(PreferenceConstants.P_JENKINS_REFRESH_ENABLED);
        int secs = CloudBeesUIPlugin.getDefault().getPreferenceStore()
                .getInt(PreferenceConstants.P_JENKINS_REFRESH_INTERVAL);
        if (!enabled || secs <= 0) {
            return; // disabled
        }

        this.regularRefresher = new Runnable() {

            @Override
            public void run() {
                if (JobsView.this.regularRefresher == null) {
                    return;
                }
                try {
                    CloudBeesDevUiPlugin.getDefault().showJobs(JobsView.this.actionReloadJobs.viewUrl, false);
                } catch (CloudBeesException e) {
                    CloudBeesUIPlugin.getDefault().getLogger().error(e);
                } finally {
                    if (JobsView.this.regularRefresher != null) { // not already stopped
                        boolean enabled = CloudBeesUIPlugin.getDefault().getPreferenceStore()
                                .getBoolean(PreferenceConstants.P_JENKINS_REFRESH_ENABLED);
                        int secs = CloudBeesUIPlugin.getDefault().getPreferenceStore()
                                .getInt(PreferenceConstants.P_JENKINS_REFRESH_INTERVAL);
                        if (enabled && secs > 0) {
                            PlatformUI.getWorkbench().getDisplay().timerExec(secs * 1000,
                                    JobsView.this.regularRefresher);
                        } else {
                            stopRefresher();
                        }
                    }
                }
            }
        };

        PlatformUI.getWorkbench().getDisplay().timerExec(secs * 1000, this.regularRefresher);
    }

    class NameSorter extends ViewerSorter {

        @Override
        public int compare(final Viewer viewer, Object e1, Object e2) {

            if (e1 instanceof JobHolder && e2 instanceof JobHolder) {
                e1 = ((JobHolder) e1).job;
                e2 = ((JobHolder) e2).job;
            }

            if (e1 instanceof JenkinsJobsResponse.JobViewGeneric
                    && e2 instanceof JenkinsJobsResponse.JobViewGeneric) {
                JenkinsJobsResponse.JobViewGeneric j1 = (JenkinsJobsResponse.JobViewGeneric) e1;
                JenkinsJobsResponse.JobViewGeneric j2 = (JenkinsJobsResponse.JobViewGeneric) e2;

                String displayName1 = j1.getName();
                String displayName2 = j2.getName();
                if (displayName1 != null && displayName2 != null) {
                    return displayName1.toLowerCase().compareTo(displayName2.toLowerCase());
                }

            }

            return super.compare(viewer, e1, e2);
        }

    }

    @Override
    public void createPartControl(final Composite parent) {

        initImages();

        this.treeViewer = new TreeViewer(parent,
                SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION | SWT.VIRTUAL);
        treeViewer.getTree().setHeaderVisible(true);

        initColumns();

        this.contentProvider = new JobsContentProvider(getViewSite());

        this.treeViewer.setContentProvider(this.contentProvider);
        this.treeViewer.setSorter(new JobSorter(JobSorter.JOB));
        this.treeViewer.setInput(getViewSite());
        this.treeViewer.addOpenListener(new IOpenListener() {

            @Override
            public void open(final OpenEvent event) {
                ISelection sel = event.getSelection();

                if (sel instanceof IStructuredSelection) {
                    Object el = ((IStructuredSelection) sel).getFirstElement();
                    if (el instanceof JobHolder && ((JobHolder) el).job instanceof Job) {
                        Job job = (Job) ((JobHolder) el).job;

                        //assuming it's a folder..
                        if (job.isFolderOrView()) {
                            JobsView.this.toggle(el);
                            return; // do nothing
                        } else {
                            CloudBeesDevUiPlugin.getDefault().showBuildForJob(job);
                        }

                    }

                    if (el instanceof JobHolder && ((JobHolder) el).job instanceof View) {
                        JobsView.this.toggle(el);
                        return; // do nothing
                    }

                }

            }

        });

        makeActions();
        contributeToActionBars();

        MenuManager popupMenu = new MenuManager();

        popupMenu.add(this.actionOpenLastBuildDetails);
        popupMenu.add(this.actionOpenLog);
        popupMenu.add(this.actionOpenBuildHistory);
        popupMenu.add(new Separator());
        popupMenu.add(this.actionOpenJobInBrowser);
        popupMenu.add(this.actionInvokeBuild);
        popupMenu.add(new Separator());
        popupMenu.add(this.actionDeleteJob);
        popupMenu.add(new Separator());
        popupMenu.add(this.actionReloadJobs);
        Menu menu = popupMenu.createContextMenu(treeViewer.getTree());
        treeViewer.getTree().setMenu(menu);
        treeViewer.getTree().setSortDirection(SWT.DOWN);

        treeViewer.addTreeListener(new ITreeViewerListener() {
            @Override
            public void treeExpanded(TreeExpansionEvent event) {
            }

            @Override
            public void treeCollapsed(TreeExpansionEvent event) {
                // if something was collapsed make sure it won't be expanded by deferred loaders
                contentProvider.removeDeferredExpanders(event.getElement());
            }
        });

        this.treeViewer.addPostSelectionChangedListener(new ISelectionChangedListener() {

            @Override
            public void selectionChanged(final SelectionChangedEvent event) {
                StructuredSelection sel = (StructuredSelection) event.getSelection();
                if (sel.getFirstElement() instanceof JobHolder) {
                    setSelectedJob((JobHolder) sel.getFirstElement());
                }
            }
        });

        this.jenkinsChangeListener = new CBRemoteChangeAdapter() {

            public void activeAccountChanged(String email, String newAccountName) {
                // if cloud-hosted view and account changed, close this view

                boolean closeView = false;

                if (viewUrl == null || viewUrl.length() == 0) {
                    closeView = true;
                }

                if (!closeView) {
                    JenkinsService ss = CloudBeesUIPlugin.getDefault().getJenkinsServiceForUrl(viewUrl);

                    if (ss == null || ss.isCloud()) {
                        closeView = true;
                    }
                }

                if (closeView) {
                    PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                        public void run() {
                            IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                                    .getActivePage();
                            page.hideView(JobsView.this);
                        }
                    });
                }
            }

            public void activeJobViewChanged(final JenkinsJobsResponse newView) {
                PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                    public void run() {
                        JobsView.this.setInput(newView);
                    }
                });
            }

            public void jenkinsStatusUpdate(String viewUrl, boolean online) {
                if (!isCurrentView(viewUrl)) {
                    return; // nothing to do, same view
                }

                // For now server status change is only relevant when it's offline
                if (!online) {
                    final JenkinsService s = CloudBeesUIPlugin.getDefault().getJenkinsServiceForUrl(viewUrl);
                    PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                        public void run() {
                            setPartName("Build Jobs [" + s.getLabel() + "] (offline)");
                            setContentDescription(" " + s.getLabel() + " (" + s.getUrl() + ") not available ("
                                    + new Date() + ")");
                        }
                    });

                }

            }

        };

        CloudBeesUIPlugin.getDefault().addCBRemoteChangeListener(this.jenkinsChangeListener);
    }

    protected void toggle(Object el) {
        boolean exp = treeViewer.getExpandedState(el);
        if (exp) {
            treeViewer.collapseToLevel(el, 1);
        } else {
            treeViewer.expandToLevel(el, 1);
        }
    }

    protected String formatBuildInfo(final JenkinsBuild build) {
        String unit = "";
        if (build.duration != null) {
            //TODO Implement proper human-readable duration conversion, consider using the same conversion rules that Jenkins uses
            //CloudBeesUIPlugin.getDefault().getLogger().info("DURATION: " + build.timestamp);
            unit = Utils.humanReadableTime((System.currentTimeMillis() - build.timestamp.longValue()));
        }
        String timeComp = build.duration != null ? /*", " + */unit + " ago" : "";
        //String buildComp = build.number != null ? "#" + build.number : "n/a";
        String buildComp = build.number != null ? " #" + build.number : "n/a";
        return timeComp + buildComp;
    }

    private void initImages() {

        String[] icons = { "blue", "red", "yellow", "grey" };

        for (int i = 0; i < icons.length; i++) {
            //TODO Refactor to use CBImages!
            Image img = ImageDescriptor.createFromURL(CloudBeesDevUiPlugin.getDefault().getBundle()
                    .getResource("/icons/jenkins-icons/16x16/" + icons[i] + ".gif")).createImage();
            this.stateIcons.put(icons[i], img);
        }

        this.stateIcons.put("disabled", ImageDescriptor.createFromURL(
                CloudBeesDevUiPlugin.getDefault().getBundle().getResource("/icons/jenkins-icons/16x16/grey.gif"))
                .createImage());

        this.stateIcons.put("folder", ImageDescriptor.createFromURL(
                CloudBeesDevUiPlugin.getDefault().getBundle().getResource("/icons/jenkins-icons/16x16/folder.gif"))
                .createImage());

        this.stateIcons.put("aborted", ImageDescriptor.createFromURL(
                CloudBeesDevUiPlugin.getDefault().getBundle().getResource("/icons/jenkins-icons/16x16/stop.gif"))
                .createImage());
    }

    /* private TreeViewerColumn createColumn(final String colName, final int width, ColumnLabelProvider labelProvider) {
       return createColumn(colName, width, -1, labelProvider);
     }*/

    private TreeViewerColumn createColumn(final String colName, final int width, final int sortCol,
            ColumnLabelProvider labelProvider) {
        final TreeViewerColumn col = new TreeViewerColumn(treeViewer, SWT.NONE);

        col.setLabelProvider(labelProvider);

        if (width > 0) {
            col.getColumn().setWidth(width);
        }

        col.getColumn().setText(colName);
        col.getColumn().setMoveable(true);

        if (sortCol >= 0) {

            col.getColumn().addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {

                    int newOrder = SWT.DOWN;

                    if (treeViewer.getTree().getSortColumn().equals(col)
                            && treeViewer.getTree().getSortDirection() == SWT.DOWN) {
                        newOrder = SWT.UP;
                    }

                    treeViewer.getTree().setSortColumn(col.getColumn());
                    treeViewer.getTree().setSortDirection(newOrder);
                    JobSorter newSorter = new JobSorter(sortCol);
                    newSorter.setDirection(newOrder);
                    JobsView.this.treeViewer.setSorter(newSorter);
                }
            });
        }
        return col;
    }

    private void contributeToActionBars() {
        IActionBars bars = getViewSite().getActionBars();
        fillLocalPullDown(bars.getMenuManager());
        fillLocalToolBar(bars.getToolBarManager());
    }

    private void fillLocalToolBar(final IToolBarManager manager) {
        //manager.add(this.actionOpenLastBuildDetails);
        manager.add(this.actionOpenJobInBrowser);
        manager.add(this.actionInvokeBuild);
        manager.add(new Separator());
        manager.add(this.actionReloadJobs);
    }

    private void fillLocalPullDown(final IMenuManager manager) {
        manager.add(this.actionOpenLastBuildDetails);
        manager.add(this.actionOpenLog);
    }

    private void makeActions() {

        CloudBeesUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);

        this.actionReloadJobs = new ReloadJobsAction();
        this.actionReloadJobs.setEnabled(false);

        this.actionOpenLastBuildDetails = new OpenBuildAction(true);
        this.actionOpenBuildHistory = new ReloadBuildHistoryAction(false);
        this.actionOpenLog = new OpenLogAction();
        this.actionDeleteJob = new DeleteJobAction(this);

        this.actionOpenJobInBrowser = new Action("Open with Browser...", Action.AS_PUSH_BUTTON | SWT.NO_FOCUS) { //$NON-NLS-1$
            @Override
            public void run() {
                if (JobsView.this.selectedJobHolder != null
                        && getJob(JobsView.this.selectedJobHolder) instanceof JobViewGeneric) {
                    JenkinsJobsResponse.JobViewGeneric job = getJob(JobsView.this.selectedJobHolder);
                    CloudBeesUIPlugin.getDefault().openWithBrowser(job.getUrl());
                }
            }
        };

        this.actionOpenJobInBrowser.setToolTipText("Open with Browser"); //TODO i18n
        this.actionOpenJobInBrowser
                .setImageDescriptor(CloudBeesDevUiPlugin.getImageDescription(CBDEVImages.IMG_BROWSER));
        this.actionOpenJobInBrowser.setEnabled(false);

        this.actionInvokeBuild = new InvokeBuildAction();

        CloudBeesUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
    }

    @Override
    public void setFocus() {
        this.treeViewer.getControl().setFocus();
    }

    @Override
    public void propertyChange(final PropertyChangeEvent event) {

        if (PreferenceConstants.P_JENKINS_INSTANCES.equals(event.getProperty())
                || PreferenceConstants.P_EMAIL.equals(event.getProperty())
                || PreferenceConstants.P_PASSWORD.equals(event.getProperty())) {
            try {

                if (viewUrl != null) {
                    CloudBeesDevUiPlugin.getDefault().showJobs(this.viewUrl, false);
                }

            } catch (CloudBeesException e) {
                //TODO i18n
                CloudBeesUIPlugin.showError("Failed to reload Jenkins jobs!", e);
            }
        }

        if (PreferenceConstants.P_JENKINS_REFRESH_INTERVAL.equals(event.getProperty())
                || PreferenceConstants.P_JENKINS_REFRESH_ENABLED.equals(event.getProperty())) {
            boolean enabled = CloudBeesUIPlugin.getDefault().getPreferenceStore()
                    .getBoolean(PreferenceConstants.P_JENKINS_REFRESH_ENABLED);
            int secs = CloudBeesUIPlugin.getDefault().getPreferenceStore()
                    .getInt(PreferenceConstants.P_JENKINS_REFRESH_INTERVAL);
            if (enabled && secs > 0) {
                startRefresher(); // start it if it was disabled by 0 value, do nothing if it was already running
            } else {
                stopRefresher();
            }
        }
    }

    @Override
    public void dispose() {
        CloudBeesUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
        CloudBeesUIPlugin.getDefault().removeCBRemoteChangeListener(this.jenkinsChangeListener);
        this.jenkinsChangeListener = null;
        stopRefresher();

        disposeImages();

        super.dispose();
    }

    private void disposeImages() {
        Iterator<Image> it = this.stateIcons.values().iterator();
        while (it.hasNext()) {
            Image img = it.next();
            img.dispose();
        }
        this.stateIcons.clear();
    }

    private void initColumns() {

        TreeViewerColumn namecol = createColumn("Job", 250, JobSorter.JOB, new ColumnLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {

                Object el = cell.getViewerRow().getElement();

                if (el instanceof PendingUpdateAdapter) {
                    PendingUpdateAdapter uel = (PendingUpdateAdapter) el;
                    cell.setText(uel.getLabel(null));
                    cell.setImage(null);
                    return;
                }

                JobViewGeneric vg = ((JobHolder) cell.getViewerRow().getElement()).job;
                if (vg instanceof JenkinsJobsResponse.Job) {
                    JenkinsJobsResponse.Job job = (Job) vg;
                    String val = job.getDisplayName();
                    if (job.inQueue != null && job.inQueue) {
                        val = val + " (in queue)";
                    } else if (job.color != null && job.color.indexOf('_') > 0) {
                        val = val + " (running)";
                    }
                    cell.setText(val);

                    String key = job.color;
                    if (job.color != null && job.color.contains("_")) {
                        key = job.color.substring(0, job.color.indexOf("_"));
                    }

                    // Assuming for now that these are all folders as thers's no API to tell the difference
                    if (job.color == null) {
                        key = "folder";
                    }

                    Image img = JobsView.this.stateIcons.get(key);
                    if (img != null) {
                        cell.setImage(img);
                    } else if (job.color != null) {
                        cell.setText(val + "[" + job.color + "]");
                    }

                }

                if (vg instanceof View) {
                    cell.setImage(CloudBeesDevUiPlugin.getImage(CBDEVImages.IMG_VIEWR2));
                    cell.setText(vg.getName());
                }

            }
        });

        createColumn("Build stability", 250, JobSorter.BUILD_STABILITY, new ColumnLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {

                Object element = getJob(cell.getViewerRow().getElement());
                if ((!(element instanceof JobViewGeneric))
                        || (element instanceof JobViewGeneric) && ((JobViewGeneric) element).isFolderOrView()) {
                    cell.setText("");
                    cell.setImage(null);
                    return;
                }

                JenkinsJobsResponse.Job job = (Job) element;

                cell.setText("");
                cell.setImage(null);

                try {
                    if (job.healthReport != null) {
                        for (int h = 0; h < job.healthReport.length; h++) {
                            String icon = job.healthReport[h].iconUrl;
                            String desc = job.healthReport[h].description;
                            String matchStr = "Build stability: ";
                            if (desc != null && desc.startsWith(matchStr)) {
                                cell.setText(" " + desc.substring(matchStr.length()));
                                cell.setImage(CloudBeesDevUiPlugin
                                        .getImage(CBDEVImages.IMG_HEALTH_PREFIX + CBDEVImages.IMG_16 + icon));
                            }
                        }
                    }
                } catch (Throwable t) {
                    t.printStackTrace();
                }

            }
        });

        createColumn("Last build", 150, JobSorter.LAST_BUILD, new ColumnLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {
                Object element = getJob(cell.getViewerRow().getElement());
                if ((!(element instanceof JobViewGeneric))
                        || (element instanceof JobViewGeneric) && ((JobViewGeneric) element).isFolderOrView()) {
                    cell.setText("");
                    cell.setImage(null);
                    return;
                }

                JenkinsJobsResponse.Job job = (Job) element;

                try {
                    cell.setText(JobsView.this.formatBuildInfo(job.lastBuild));
                } catch (Throwable t) {
                    cell.setText("");
                }
            }
        });
        createColumn("Last success", 150, JobSorter.LAST_SUCCESS, new ColumnLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {
                Object element = getJob(cell.getViewerRow().getElement());
                if ((!(element instanceof JobViewGeneric))
                        || (element instanceof JobViewGeneric) && ((JobViewGeneric) element).isFolderOrView()) {
                    cell.setText("");
                    cell.setImage(null);
                    return;
                }

                JenkinsJobsResponse.Job job = (Job) element;

                try {
                    cell.setText(JobsView.this.formatBuildInfo(job.lastSuccessfulBuild));
                } catch (Throwable t) {
                    cell.setText("");
                }
            }
        });
        createColumn("Last failure", 150, JobSorter.LAST_FAILURE, new ColumnLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {
                Object element = getJob(cell.getViewerRow().getElement());
                if ((!(element instanceof JobViewGeneric))
                        || (element instanceof JobViewGeneric) && ((JobViewGeneric) element).isFolderOrView()) {
                    cell.setText("");
                    cell.setImage(null);
                    return;
                }

                JenkinsJobsResponse.Job job = (Job) element;

                try {
                    cell.setText(JobsView.this.formatBuildInfo(job.lastFailedBuild));
                } catch (Throwable t) {
                    cell.setText("");
                }
            }
        });

    }

}