org.sonar.ide.eclipse.ui.internal.wizards.associate.ConfigureProjectsPage.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.ide.eclipse.ui.internal.wizards.associate.ConfigureProjectsPage.java

Source

/*
 * SonarQube Eclipse
 * Copyright (C) 2010-2014 SonarSource
 * dev@sonar.codehaus.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package org.sonar.ide.eclipse.ui.internal.wizards.associate;

import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.bindings.keys.IKeyLookup;
import org.eclipse.jface.bindings.keys.KeyLookupFactory;
import org.eclipse.jface.databinding.viewers.ViewerSupport;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.FocusCellHighlighter;
import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.jface.viewers.TableViewerFocusCellManager;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.PlatformUI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.ide.eclipse.common.servers.ISonarServer;
import org.sonar.ide.eclipse.core.internal.SonarCorePlugin;
import org.sonar.ide.eclipse.core.internal.SonarNature;
import org.sonar.ide.eclipse.core.internal.jobs.SynchronizeAllIssuesJob;
import org.sonar.ide.eclipse.core.internal.resources.SonarProject;
import org.sonar.ide.eclipse.ui.internal.SonarImages;
import org.sonar.ide.eclipse.ui.internal.SonarUiPlugin;
import org.sonar.ide.eclipse.ui.internal.console.SonarConsole;
import org.sonar.ide.eclipse.wsclient.ConnectionException;
import org.sonar.ide.eclipse.wsclient.ISonarRemoteModule;
import org.sonar.ide.eclipse.wsclient.WSClientFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ConfigureProjectsPage extends WizardPage {

    private static final Logger LOG = LoggerFactory.getLogger(ConfigureProjectsPage.class);

    private final List<IProject> projects;
    private TableViewer viewer;
    private final Collection<ISonarServer> sonarServers;
    private boolean alreadyRun = false;

    public ConfigureProjectsPage(List<IProject> projects) {
        super("configureProjects", "Associate with SonarQube", SonarImages.SONARWIZBAN_IMG);
        setDescription("Select projects to add SonarQube capability.");
        this.projects = projects;
        sonarServers = SonarCorePlugin.getServersManager().getServers();
    }

    @Override
    public void createControl(Composite parent) {
        PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, SonarUiPlugin.PLUGIN_ID + ".help_associate");

        Composite container = new Composite(parent, SWT.NONE);

        GridLayout layout = new GridLayout();
        layout.numColumns = 2;
        layout.marginHeight = 0;
        layout.marginWidth = 5;
        container.setLayout(layout);

        // List of projects
        viewer = new TableViewer(container,
                SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.VIRTUAL);
        viewer.getTable().setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true, 1, 3));

        viewer.getTable().setHeaderVisible(true);

        TableViewerColumn columnProject = new TableViewerColumn(viewer, SWT.LEFT);
        columnProject.getColumn().setText("Project");
        columnProject.getColumn().setWidth(200);

        TableViewerColumn columnSonarProject = new TableViewerColumn(viewer, SWT.LEFT);
        columnSonarProject.getColumn().setText("SonarQube Project");
        columnSonarProject.getColumn().setWidth(600);

        columnSonarProject.setEditingSupport(new ProjectAssociationModelEditingSupport(viewer));

        List<ProjectAssociationModel> list = Lists.newArrayList();
        for (IProject project : projects) {
            ProjectAssociationModel sonarProject = new ProjectAssociationModel(project);
            list.add(sonarProject);
        }

        ColumnViewerEditorActivationStrategy activationSupport = createActivationSupport();

        /*
         * Without focus highlighter, keyboard events will not be delivered to
         * ColumnViewerEditorActivationStragety#isEditorActivationEvent(...) (see above)
         */
        FocusCellHighlighter focusCellHighlighter = new FocusCellOwnerDrawHighlighter(viewer);
        TableViewerFocusCellManager focusCellManager = new TableViewerFocusCellManager(viewer,
                focusCellHighlighter);

        TableViewerEditor.create(viewer, focusCellManager, activationSupport,
                ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION);

        ViewerSupport.bind(viewer, new WritableList(list, ProjectAssociationModel.class),
                new IValueProperty[] {
                        BeanProperties.value(ProjectAssociationModel.class,
                                ProjectAssociationModel.PROPERTY_PROJECT_ECLIPSE_NAME),
                        BeanProperties.value(ProjectAssociationModel.class,
                                ProjectAssociationModel.PROPERTY_PROJECT_SONAR_FULLNAME) });

        scheduleAutomaticAssociation();

        setControl(container);
    }

    private ColumnViewerEditorActivationStrategy createActivationSupport() {
        ColumnViewerEditorActivationStrategy activationSupport = new ColumnViewerEditorActivationStrategy(viewer) {
            @Override
            protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
                return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
                        || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION
                        || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC
                        || event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED
                                && event.keyCode == KeyLookupFactory.getDefault()
                                        .formalKeyLookup(IKeyLookup.F2_NAME);
            }
        };
        activationSupport.setEnableEditorActivationWithKeyboard(true);
        return activationSupport;
    }

    private void scheduleAutomaticAssociation() {
        getShell().addShellListener(new ShellAdapter() {
            @Override
            public void shellActivated(ShellEvent shellevent) {
                if (!alreadyRun) {
                    alreadyRun = true;
                    try {
                        if (sonarServers.isEmpty()) {
                            setMessage("Please configure a SonarQube server first", IMessageProvider.ERROR);
                        } else {
                            setMessage("", IMessageProvider.NONE);
                            getWizard().getContainer().run(true, false,
                                    new AssociateProjects(sonarServers, getProjects()));
                        }
                    } catch (InvocationTargetException ex) {
                        LOG.error(ex.getMessage(), ex);
                        if (ex.getTargetException() instanceof ConnectionException) {
                            setMessage(
                                    "One of your SonarQube server cannot be reached. Please check your connection settings.",
                                    IMessageProvider.ERROR);
                        } else {
                            setMessage("Error: " + ex.getMessage(), IMessageProvider.ERROR);
                        }
                    } catch (Exception ex) {
                        LOG.error(ex.getMessage(), ex);
                        setMessage("Error: " + ex.getMessage(), IMessageProvider.ERROR);
                    }
                }
            }
        });
    }

    private class ProjectAssociationModelEditingSupport extends EditingSupport {

        SonarSearchEngineProvider contentProposalProvider = new SonarSearchEngineProvider(sonarServers,
                ConfigureProjectsPage.this);

        public ProjectAssociationModelEditingSupport(TableViewer viewer) {
            super(viewer);
        }

        @Override
        protected boolean canEdit(Object element) {
            return element instanceof ProjectAssociationModel;
        }

        @Override
        protected CellEditor getCellEditor(Object element) {
            return new TextCellEditorWithContentProposal(viewer.getTable(), contentProposalProvider,
                    (ProjectAssociationModel) element);
        }

        @Override
        protected Object getValue(Object element) {
            return StringUtils.trimToEmpty(((ProjectAssociationModel) element).getSonarProjectName());
        }

        @Override
        protected void setValue(Object element, Object value) {
            // Don't set value as the model was already updated in the text adapter
        }

    }

    /**
     * Update all Eclipse projects when an association was provided:
     *   - enable Sonar nature
     *   - update sonar URL / key
     *   - refresh issues if necessary
     * @return
     */
    public boolean finish() {
        final ProjectAssociationModel[] projectAssociations = getProjects();
        for (ProjectAssociationModel projectAssociation : projectAssociations) {
            if (StringUtils.isNotBlank(projectAssociation.getKey())) {
                try {
                    boolean changed = false;
                    IProject project = projectAssociation.getProject();
                    SonarProject sonarProject = SonarProject.getInstance(project);
                    if (!projectAssociation.getUrl().equals(sonarProject.getUrl())) {
                        sonarProject.setUrl(projectAssociation.getUrl());
                        changed = true;
                    }
                    if (!projectAssociation.getKey().equals(sonarProject.getKey())) {
                        sonarProject.setKey(projectAssociation.getKey());
                        changed = true;
                    }
                    if (changed) {
                        sonarProject.save();
                    }
                    if (!SonarNature.hasSonarNature(project)) {
                        SonarNature.enableNature(project);
                        changed = true;
                    }
                    if (changed) {
                        boolean debugEnabled = SonarConsole.isDebugEnabled();
                        SynchronizeAllIssuesJob.createAndSchedule(project, debugEnabled,
                                SonarUiPlugin.getExtraPropertiesForLocalAnalysis(project),
                                SonarUiPlugin.getSonarJvmArgs(), SonarUiPlugin.isForceFullPreview());
                    }
                } catch (CoreException e) {
                    LOG.error(e.getMessage(), e);
                    return false;
                }
            }
        }
        return true;
    }

    private ProjectAssociationModel[] getProjects() {
        WritableList projectAssociations = (WritableList) viewer.getInput();
        return (ProjectAssociationModel[]) projectAssociations
                .toArray(new ProjectAssociationModel[projectAssociations.size()]);
    }

    public static class AssociateProjects implements IRunnableWithProgress {

        private final Collection<ISonarServer> sonarServers;
        private final ProjectAssociationModel[] projectAssociations;

        public AssociateProjects(Collection<ISonarServer> sonarServers, ProjectAssociationModel[] projects) {
            Assert.isNotNull(sonarServers);
            Assert.isNotNull(projects);
            this.sonarServers = sonarServers;
            this.projectAssociations = projects;
        }

        public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
            monitor.beginTask("Associating SonarQube projects", IProgressMonitor.UNKNOWN);
            // Retrieve list of all remote projects
            Map<String, List<ISonarRemoteModule>> remoteSonarProjects = fetchAllRemoteSonarModules();

            // Verify that all projects already associated are found on remote. If not found projects are considered as unassociated.
            validateProjectAssociations(remoteSonarProjects);

            // Now check for all potential matches for a all non associated projects on all Sonar servers
            Map<ProjectAssociationModel, List<PotentialMatchForProject>> potentialMatches = findAllPotentialMatches(
                    remoteSonarProjects);

            // Now for each project try to find the better match
            findBestMatchAndAssociate(potentialMatches);

            monitor.done();
        }

        private void findBestMatchAndAssociate(
                Map<ProjectAssociationModel, List<PotentialMatchForProject>> potentialMatches) {
            for (Map.Entry<ProjectAssociationModel, List<PotentialMatchForProject>> entry : potentialMatches
                    .entrySet()) {
                List<PotentialMatchForProject> potentialMatchesForProject = entry.getValue();
                if (!potentialMatchesForProject.isEmpty()) {
                    // Take the better choice according to Levenshtein distance
                    PotentialMatchForProject best = potentialMatchesForProject.get(0);
                    int currentBestDistance = StringUtils.getLevenshteinDistance(best.getResource().getKey(),
                            entry.getKey().getEclipseName());
                    for (PotentialMatchForProject potentialMatch : potentialMatchesForProject) {
                        int distance = StringUtils.getLevenshteinDistance(potentialMatch.getResource().getKey(),
                                entry.getKey().getEclipseName());
                        if (distance < currentBestDistance) {
                            best = potentialMatch;
                            currentBestDistance = distance;
                        }
                    }
                    entry.getKey().associate(best.getHost(), best.getResource().getName(),
                            best.getResource().getKey());
                }
            }
        }

        private Map<ProjectAssociationModel, List<PotentialMatchForProject>> findAllPotentialMatches(
                Map<String, List<ISonarRemoteModule>> remoteSonarProjects) {
            Map<ProjectAssociationModel, List<PotentialMatchForProject>> potentialMatches = new HashMap<ProjectAssociationModel, List<PotentialMatchForProject>>();
            for (Map.Entry<String, List<ISonarRemoteModule>> entry : remoteSonarProjects.entrySet()) {
                String url = entry.getKey();
                List<ISonarRemoteModule> resources = entry.getValue();
                for (ProjectAssociationModel sonarProject : projectAssociations) {
                    if (StringUtils.isBlank(sonarProject.getKey())) {
                        // Not associated yet
                        if (!potentialMatches.containsKey(sonarProject)) {
                            potentialMatches.put(sonarProject, new ArrayList<PotentialMatchForProject>());
                        }
                        for (ISonarRemoteModule resource : resources) {
                            // A resource is a potential match if resource key contains Eclipse name
                            if (resource.getKey().contains(sonarProject.getEclipseName())) {
                                potentialMatches.get(sonarProject).add(new PotentialMatchForProject(resource, url));
                            }
                        }
                    }
                }
            }
            return potentialMatches;
        }

        private void validateProjectAssociations(Map<String, List<ISonarRemoteModule>> remoteSonarProjects) {
            for (ProjectAssociationModel projectAssociation : projectAssociations) {
                if (SonarNature.hasSonarNature(projectAssociation.getProject())) {
                    SonarProject sonarProject = SonarProject.getInstance(projectAssociation.getProject());
                    String key = sonarProject.getKey();
                    String url = sonarProject.getUrl();
                    validateProjectAssociation(remoteSonarProjects, projectAssociation, key, url);
                }
            }
        }

        private void validateProjectAssociation(Map<String, List<ISonarRemoteModule>> remoteSonarProjects,
                ProjectAssociationModel projectAssociation, String key, String url) {
            boolean found = false;
            if (remoteSonarProjects.containsKey(url)) {
                for (ISonarRemoteModule remoteProject : remoteSonarProjects.get(url)) {
                    if (remoteProject.getKey().equals(key)) {
                        found = true;
                        // Call associate to have the name
                        projectAssociation.associate(url, remoteProject.getName(), key);
                        break;
                    }
                }
            }
            if (!found) {
                // There is no Sonar server with the provided URL or not matching project so consider the project is not associated
                projectAssociation.unassociate();
            }
        }

        private Map<String, List<ISonarRemoteModule>> fetchAllRemoteSonarModules() {
            Map<String, List<ISonarRemoteModule>> remoteSonarModules = new HashMap<String, List<ISonarRemoteModule>>();
            for (ISonarServer sonarServer : sonarServers) {
                List<ISonarRemoteModule> remoteModules = WSClientFactory.getSonarClient(sonarServer)
                        .listAllRemoteModules();
                remoteSonarModules.put(sonarServer.getUrl(), remoteModules);
            }
            return remoteSonarModules;
        }

        private static class PotentialMatchForProject {
            private ISonarRemoteModule resource;
            private String host;

            public PotentialMatchForProject(ISonarRemoteModule resource, String host) {
                super();
                this.resource = resource;
                this.host = host;
            }

            public ISonarRemoteModule getResource() {
                return resource;
            }

            public String getHost() {
                return host;
            }

        }
    }

}