org.springsource.ide.eclipse.gradle.core.GradleProject.java Source code

Java tutorial

Introduction

Here is the source code for org.springsource.ide.eclipse.gradle.core.GradleProject.java

Source

/*******************************************************************************
 * Copyright (c) 2012 VMWare, Inc.
 * 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:
 * VMWare, Inc. - initial API and implementation
 *******************************************************************************/
package org.springsource.ide.eclipse.gradle.core;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.gradle.tooling.LongRunningOperation;
import org.gradle.tooling.model.DomainObjectSet;
import org.gradle.tooling.model.GradleTask;
import org.gradle.tooling.model.eclipse.EclipseLinkedResource;
import org.gradle.tooling.model.eclipse.EclipseProject;
import org.gradle.tooling.model.eclipse.EclipseProjectDependency;
import org.gradle.tooling.model.eclipse.EclipseSourceDirectory;
import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
import org.springsource.ide.eclipse.gradle.core.GradleModelProvider.GroupedModelProvider;
import org.springsource.ide.eclipse.gradle.core.actions.GradleRefreshPreferences;
import org.springsource.ide.eclipse.gradle.core.classpathcontainer.FastOperationFailedException;
import org.springsource.ide.eclipse.gradle.core.classpathcontainer.GradleClassPathContainer;
import org.springsource.ide.eclipse.gradle.core.classpathcontainer.GradleClasspathContainerInitializer;
import org.springsource.ide.eclipse.gradle.core.dsld.DSLDSupport;
import org.springsource.ide.eclipse.gradle.core.launch.GradleLaunchConfigurationDelegate;
import org.springsource.ide.eclipse.gradle.core.preferences.GlobalSettings;
import org.springsource.ide.eclipse.gradle.core.preferences.GradleImportPreferences;
import org.springsource.ide.eclipse.gradle.core.preferences.GradleProjectPreferences;
import org.springsource.ide.eclipse.gradle.core.util.ErrorHandler;
import org.springsource.ide.eclipse.gradle.core.util.ExceptionUtil;
import org.springsource.ide.eclipse.gradle.core.util.GradleRunnable;
import org.springsource.ide.eclipse.gradle.core.util.IllegalClassPathEntryException;
import org.springsource.ide.eclipse.gradle.core.util.JobUtil;
import org.springsource.ide.eclipse.gradle.core.util.NatureUtils;
import org.springsource.ide.eclipse.gradle.core.wtp.WTPUtil;

/**
 * An instance of GradleProject represent a project in a Gradle build. It may be the root project
 * of the build, or it may be one of the children of the root project.
 * <p>
 * There may or may not be a corresponding IProject in the workspace, depending on whether the project
 * was imported into the workspace.
 * <p>
 * To be able to easily map between the Gradle "EclipseProject" model instances, GradleProject instances
 * and IProject instances the absolute location of the project is used as a key. Therefore,
 * no GradleProject instance should ever be created without a location.
 * 
 * @author Kris De Volder
 */
public class GradleProject {

    public static boolean DEBUG = false;

    private void debug(String msg) {
        if (DEBUG) {
            System.out.println(this + ": " + msg);
        }
    }

    /**
     * Canonical File pointing to the root of this project's location in the file system.
     * Never null.
     */
    private File location;

    /**
     * The model provider is reponsible for obtaining and cahching models from the tooling API.
     */
    private GroupedModelProvider modelProvider = null;

    /**
     * The class path container for this project is created lazily.
     */
    private GradleClassPathContainer classPathContainer = null;

    private IProject cachedProject;

    private GradleModelListeners modelListeners = new GradleModelListeners();

    private Job modelUpdateJob;

    private GradleProjectPreferences preferences;
    private GradleImportPreferences importPrefs;

    private GradleRefreshPreferences refreshPrefs;

    private GradleDependencyComputer dependencyComputer = new GradleDependencyComputer(this);

    public GradleProject(File canonicalFile) {
        Assert.isLegal(canonicalFile != null, "Project location must not be null");
        Assert.isLegal(canonicalFile.exists(), "Project location doesn't exist");
        Assert.isLegal(canonicalFile.isAbsolute(), "Project location must be absolute");
        Assert.isLegal(canonicalFile.isDirectory(), "Project location must be a directory");
        this.location = canonicalFile;
    }

    /**
     * Refreshes the contents of the classpath container to bring it in synch with the gradleModel.
     * (Note that this doesn't force the gradleModel itself to be updated!)
     */
    public void refreshDependencies(IProgressMonitor monitor) throws CoreException {
        monitor.beginTask("Refresh dependencies " + getName(), 3);
        try {
            refreshProjectDependencies(ProjectMapperFactory.workspaceMapper(), new SubProgressMonitor(monitor, 1));
            refreshClasspathContainer(new SubProgressMonitor(monitor, 1));
            //         WTPUtil.addWebLibraries(this); only doing this on project import for now.
        } finally {
            monitor.done();
        }
    }

    /**
     * Refreshes the project dependencies, bringing then in synch with the gradleModel.
     * (Note that this doesn't force the gradleModel itself to be updated!)
     */
    private void refreshProjectDependencies(IProjectMapper projectMapper, SubProgressMonitor monitor)
            throws OperationCanceledException, CoreException {
        monitor.beginTask("Refresh project dependencies " + getName(), 2);
        try {
            EclipseProject projectModel = getGradleModel(new SubProgressMonitor(monitor, 1));
            setProjectDependencies(projectModel, projectMapper, new SubProgressMonitor(monitor, 1));
        } finally {
            monitor.done();
        }
        //      if (GlobalSettings.DEBUG) {
        //         printDependencyGraph();
        //      }
    }

    /**
     * Refreshes the contents of the classpath container to bring it in synch with the gradleModel.
     * (Note that this doesn't force the gradleModel itself to be updated!)
     */
    private void refreshClasspathContainer(IProgressMonitor monitor) throws CoreException {
        monitor.beginTask("Refresh Classpath Container", 1);
        try {
            IProject project = getProject();
            if (project != null) {
                //TODO: the requestUpdateFor is asynchronous... make it synchronous!
                GradleClasspathContainerInitializer.requestUpdateFor(project, false);
            }
        } finally {
            monitor.done();
        }
    }

    /**
     * Reconfigures the project's source folders in Java classpath based on current gradle model.
     * (Note that this doesn't force the gradle model itself to be updated!)
     */
    public void refreshSourceFolders(ErrorHandler eh, IProgressMonitor monitor)
            throws OperationCanceledException, CoreException {
        monitor.beginTask("Refreshing source folders", 3);
        try {
            HierarchicalEclipseProject projectModel = getSkeletalGradleModel(new SubProgressMonitor(monitor, 1));

            try {
                DomainObjectSet<? extends EclipseLinkedResource> linkedResources = projectModel
                        .getLinkedResources();
                setLinkedResources(linkedResources.getAll(), eh, new SubProgressMonitor(monitor, 1));
            } catch (UnsupportedOperationException e) {
                //Probably using too old a verion of Gradle... this is not serious but could be a problem for some users.
                //Record a warning and proceed.
                eh.handle(IStatus.WARNING, e);
            }

            DomainObjectSet<? extends EclipseSourceDirectory> sourceDirs = projectModel.getSourceDirectories();
            setSourceFolders(sourceDirs.getAll(), eh, new SubProgressMonitor(monitor, 1));
        } finally {
            monitor.done();
        }
    }

    private void setLinkedResources(List<? extends EclipseLinkedResource> linkedResources, ErrorHandler eh,
            IProgressMonitor mon) {

        //TODO: only delete old linked resources if they are changing.

        IProject project = getProject();
        Assert.isNotNull(project); //Must have an associated eclipse project to create linked Eclipse resources.
        Collection<IResource> existingLinkedResources = getLinkedResources();

        //Delete all the old linked resources
        for (IResource r : existingLinkedResources) {
            try {
                if (r.exists() && r.isLinked()) {
                    r.delete(true, null);
                }
            } catch (CoreException e) {
                eh.handleError(e);
            }
        }

        mon.beginTask("Create linked resources", linkedResources.size());
        List<IResource> createdResources = new ArrayList<IResource>();
        try {

            //Create new ones
            for (EclipseLinkedResource linkedResource : linkedResources) {
                try {
                    int type = Integer.valueOf(linkedResource.getType());
                    String location = linkedResource.getLocation();
                    if (location != null) {
                        if (type == 1) {
                            IFile f = project.getFile(new Path(linkedResource.getName()));
                            if (f.exists() && f.isLinked()) {
                                //This shouldn't happen, but it might if, for some reason project state became inconsistent,
                                //perhaps because of a Eclipse crash or something like that. So we try to deal with it.
                                f.delete(true, null);
                            }
                            f.createLink(new Path(location), IResource.ALLOW_MISSING_LOCAL, null);
                            createdResources.add(f);
                        } else if (type == 2) {
                            //Folder
                            IFolder f = project.getFolder(new Path(linkedResource.getName()));
                            if (f.exists() && f.isLinked()) {
                                //This shouldn't happen, but it might if, for some reason project state became inconsitent,
                                //perhaps because of a Eclipse crash or something like that. So we try to deal with it.
                                f.delete(true, null);
                            }
                            f.createLink(new Path(location), IResource.ALLOW_MISSING_LOCAL, null);
                            createdResources.add(f);
                        } else {
                            eh.handleError(new IllegalStateException("Unknown linked resource type: " + type));
                        }
                    } else {
                        location = linkedResource.getLocationUri();
                        if (location != null) {
                            eh.handleError(
                                    new Error("Not implemented: linked resource with URI '" + location + "'"));
                        } else {
                            eh.handleError(new IllegalStateException(
                                    "linked resource has neither location nor locationURI " + linkedResource));
                        }
                    }
                } catch (CoreException e) {
                    eh.handleError(e);
                } finally {
                    mon.worked(1);
                }
            }
        } catch (Exception e) {
            GradleCore.log(e);
        } finally {
            getProjectPreferences().setLinkedResources(createdResources);
            mon.done();
        }

    }

    /**
     * Retrieves a collection of linked resources previously set by setLinkedResources. Other linked resources
     * might exist in the project. (E.g. if a user created them manually). I.e. only the linked resources
     * that where previously created by Gradle tooling are returned.
     */
    private Collection<IResource> getLinkedResources() {
        return getProjectPreferences().getLinkedResources();
    }

    /**
     * @return preferences associated with the corresponding eclipse project. This may return null if there is no
     * corresponding eclipse project.
     */
    public synchronized GradleProjectPreferences getProjectPreferences() {
        if (preferences == null) {
            preferences = new GradleProjectPreferences(this);
        }
        return preferences;
    }

    /**
     * @param conf May be null in contexts where there is no launch configuration (e.g. build model operations, or tasks executed for an import
     * rather than directly by the user). 
     */
    public void configureOperation(LongRunningOperation gradleOp, ILaunchConfiguration conf) {
        try {
            GradleProjectPreferences prefs = getProjectPreferences();
            File javaHome = prefs.getJavaHome();
            if (javaHome != null) {
                gradleOp.setJavaHome(javaHome);
            }
            String[] jvmArgs = prefs.getJVMArgs();
            if (jvmArgs != null) {
                gradleOp.setJvmArguments(jvmArgs);
            }
            String[] pgmArgs = prefs.getProgramArgs();
            if (pgmArgs != null) {
                gradleOp.withArguments(pgmArgs);
            }
            GradleLaunchConfigurationDelegate.configureOperation(gradleOp, conf);
        } catch (Exception e) {
            //The idea of this catch block is capture 'unsupported' operation exception
            // when running against older version of Gradle. 
            // However, the exception won't be thrown... the exception will only
            // be thrown later on during model build or task execution. 
            // That really doesn't leave us with a practical way to handle the exceptions.
            GradleCore.log(e);
        }
    }

    /**
     * Gets the import preferences for a given project... 
     * @return
     */
    public synchronized GradleImportPreferences getImportPreferences() {
        if (importPrefs == null) {
            importPrefs = new GradleImportPreferences(this);
        }
        return importPrefs;
    }

    public synchronized GradleRefreshPreferences getRefreshPreferences() {
        if (refreshPrefs == null) {
            refreshPrefs = new GradleRefreshPreferences(this);
        }
        return refreshPrefs;
    }

    private void setSourceFolders(List<? extends EclipseSourceDirectory> gradleSourceDirs, ErrorHandler eh,
            IProgressMonitor monitor) throws JavaModelException {

        IJavaProject javaProject = getJavaProject();
        IClasspathEntry[] oldClasspath = javaProject.getRawClasspath();
        int totalWork = 2 * (gradleSourceDirs.size() + oldClasspath.length);
        monitor.beginTask("Converting gradle source folders to Eclipse", totalWork);
        try {
            //To recognise and remove duplicate source entries.
            Set<String> seen = new HashSet<String>();
            //To quickly find oldEntries by path and copy over exclusion and inclusion filters.
            Map<IPath, IClasspathEntry> oldSourceEntries = new HashMap<IPath, IClasspathEntry>();
            for (IClasspathEntry e : oldClasspath) {
                if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                    oldSourceEntries.put(e.getPath(), e);
                }
            }

            //Convert gradle entries into Eclipse classpath entries
            List<IClasspathEntry> sourceEntries = new ArrayList<IClasspathEntry>();
            for (EclipseSourceDirectory gradleSourceDir : gradleSourceDirs) {
                try {
                    if (!seen.contains(gradleSourceDir.getPath())) {
                        seen.add(gradleSourceDir.getPath());
                        IClasspathEntry newSourceEntry = newSourceEntry(gradleSourceDir, oldSourceEntries);
                        if (newSourceEntry != null) {
                            sourceEntries.add(newSourceEntry);
                        }
                    }
                } catch (IllegalClassPathEntryException e) {
                    eh.handleError(e);
                }
                monitor.worked(1);
            }

            //Remove old source entries and replace with new ones.
            ClassPath newClasspath = new ClassPath(this, oldClasspath.length);
            newClasspath.addAll(sourceEntries);
            for (IClasspathEntry oldEntry : oldClasspath) {
                if (oldEntry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
                    newClasspath.add(oldEntry);
                }
                monitor.worked(1);
            }

            newClasspath.setOn(javaProject, new SubProgressMonitor(monitor, totalWork / 2));
        } finally {
            monitor.done();
        }
    }

    /**
     * @param all
     * @param subProgressMonitor
     */
    private void setProjectDependencies(EclipseProject projectModel, IProjectMapper projectMapper,
            IProgressMonitor monitor) throws JavaModelException {
        DomainObjectSet<? extends EclipseProjectDependency> projectDeps = projectModel.getProjectDependencies();
        //TODO: we remove and re-add all project dep entries, even if they didn't change. Should we optimize this to
        // only add/remove when entries have changed?

        //TODO: We could mark 'our' entries with a special classpath attribute. That way user's can add their own
        //  and we can avoid zapping them on each refresh.

        //TODO: access rules etc.. (what is that? can gradle give something like this to us, or is just using the default ok)      

        IJavaProject javaProject = getJavaProject();
        IClasspathEntry[] oldClasspath = javaProject.getRawClasspath();
        int totalWork = 2 * (projectDeps.size() + oldClasspath.length);
        monitor.beginTask("Converting gradle project dependencies to Eclipse", totalWork);
        try {
            //Convert gradle entries into Eclipse classpath entries
            List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>();
            for (EclipseProjectDependency dep : projectDeps) {
                IClasspathEntry newEntry = newProjectEntry(projectMapper, dep);
                if (newEntry != null) {
                    entries.add(newEntry);
                }
                monitor.worked(1);
            }
            for (IClasspathEntry newEntry : this.getDependencyComputer().getProjectEntries(projectModel)) {
                entries.add(newEntry);
            }

            //Remove old project entries and replace with new ones.
            ClassPath newClasspath = new ClassPath(this, entries.size());
            newClasspath.addAll(entries);
            for (IClasspathEntry oldEntry : oldClasspath) {
                if (oldEntry.getEntryKind() != IClasspathEntry.CPE_PROJECT) {
                    newClasspath.add(oldEntry);
                }
                monitor.worked(1);
            }

            newClasspath.setOn(javaProject, new SubProgressMonitor(monitor, totalWork / 2));
        } finally {
            monitor.done();
        }
    }

    private IClasspathEntry newProjectEntry(IProjectMapper projectMapper, EclipseProjectDependency dep) {
        HierarchicalEclipseProject target = dep.getTargetProject();
        IProject eclipseTarget = projectMapper.get(target);
        return JavaCore.newProjectEntry(eclipseTarget.getFullPath(), GlobalSettings.exportProjectEntries);
    }

    /**
     * Create an Eclipse source classpath entry from a Gradle source entry. May return null
     * if the entry looks invalid (e.g. the corresponding folder doesn't exist in the project)
     * @param oldEntries old source entries indexed by path, used to copy over exclusions and inclusions so they don't get lost.
     */
    private IClasspathEntry newSourceEntry(EclipseSourceDirectory gradleSourceDir,
            Map<IPath, IClasspathEntry> oldEntries) throws IllegalClassPathEntryException {
        Path gradleSourcePath = new Path(gradleSourceDir.getPath());
        try {
            IFolder srcFolder = getProject().getFolder(gradleSourcePath);
            if (!srcFolder.exists()) {
                throw new IllegalClassPathEntryException("non-existent source folder", this, gradleSourceDir);
            }
            IPath path = srcFolder.getFullPath();
            IClasspathEntry oldEntry = oldEntries.get(path);
            if (oldEntry == null) {
                return JavaCore.newSourceEntry(path);
            } else {
                return JavaCore.newSourceEntry(path, oldEntry.getInclusionPatterns(),
                        oldEntry.getExclusionPatterns(), oldEntry.getOutputLocation(),
                        oldEntry.getExtraAttributes());
            }
        } catch (IllegalClassPathEntryException e) {
            throw e;
        } catch (Throwable e) {
            throw new IllegalClassPathEntryException("illegal source folder", this, gradleSourceDir, e);
        }
    }

    public GradleClassPathContainer getClassPathcontainer() {
        return classPathContainer;
    }

    /**
     * Called by {@link GradleClasspathContainerInitializer} when the class path container
     * for this project is instantiated.
     */
    public void setClassPathContainer(GradleClassPathContainer it) {
        Assert.isLegal(classPathContainer == null, "Classpath container set multiple times");
        this.classPathContainer = it;
    }

    /**
     * Return the "eclipse" name of this gradle project. That is the name of the project in the workspace
     * that corresponds to this Gradle project. 
     * <p>
     * @return Name of an IProject that exists in the workpace, or null if this project was not imported into the workspace.
     */
    public String getName() {
        IProject project = getProject();
        if (project != null) {
            return project.getName();
        }
        return null;
    }

    public EclipseProject getGradleModel() throws FastOperationFailedException, CoreException {
        return getGradleModel(EclipseProject.class);
    }

    public <T extends HierarchicalEclipseProject> T getGradleModel(Class<T> type)
            throws FastOperationFailedException, CoreException {
        GradleModelProvider provider = getModelProvider();
        T model = provider.getCachedModel(this, type);
        if (model == null) {
            throw ExceptionUtil.coreException("Could not get a Gradle model for '" + this.getDisplayName() + "'. "
                    + "This may be because the Gradle project hierarchy changed after the project was imported.");
        } else {
            return model;
        }
    }

    GroupedModelProvider getModelProvider() throws FastOperationFailedException {
        if (modelProvider == null) {
            GradleProject root = getRootProject();
            if (root == this) {
                modelProvider = GradleModelProvider.create(this);
            } else {
                modelProvider = root.getModelProvider();
            }
        }
        return modelProvider;
    }

    GroupedModelProvider getModelProvider(Class<? extends HierarchicalEclipseProject> typeHint,
            IProgressMonitor monitor) throws OperationCanceledException, CoreException {
        monitor.beginTask("Get model provider for '" + getDisplayName() + "'", 1);
        try {
            return getModelProvider();
        } catch (FastOperationFailedException e) {
            GroupedModelProvider provider = GradleModelProvider.create(this);
            provider.ensureModels(typeHint, new SubProgressMonitor(monitor, 1));
            return provider;
        } finally {
            monitor.done();
        }
    }

    /**
     * This is similar to getGradleModel except that when it fails to return a model right away,
     * it will schedule a background Job to compute the model. Typically, this method is used
     * by UI based clients that can't afford to wait for the model, but still want to make
     * sure that at least a model will become available in the future via a registered
     * {@link IGradleModelListener}.
     * @throws CoreException 
     */
    public EclipseProject requestGradleModel() throws FastOperationFailedException, CoreException {
        try {
            return getGradleModel();
        } catch (FastOperationFailedException e) {
            scheduleModelUpdate();
            throw e;
        }
    }

    private synchronized void scheduleModelUpdate() {
        if (modelUpdateJob == null) {
            //If not null, another request for the same model is already active so no need to schedule again.
            JobUtil.schedule(new GradleRunnable("Build Gradle Model for " + getDisplayName()) {
                @Override
                public void doit(IProgressMonitor mon) throws Exception {
                    try {
                        getGradleModel(mon);
                    } finally {
                        finishedModelUpdateJob();
                    }
                }
            });
        }
    }

    private synchronized void finishedModelUpdateJob() {
        modelUpdateJob = null;
    }

    /**
     * Fetch gradle model of given type, take whatever time necessary to build the model if a model
     * isn't yet available.
     * <p>
     * This method is written in such a way that it only holds thread scynh locks for short time (while
     * it is trying initially to fetch model with the fast operation. If fast operation fails, the lock
     * is release and a model update request is started. While this request is active, we should not
     * hold the thread synch lock, since that will block concurrent 'fast' operations from returning quickly.
     * @throws  
     */
    private <T extends HierarchicalEclipseProject> T getGradleModel(Class<T> type, IProgressMonitor monitor)
            throws OperationCanceledException, CoreException {
        monitor.beginTask("Get model for '" + getDisplayName() + "'", 2);
        try {
            //1:
            GradleModelProvider provider = getModelProvider(type, new SubProgressMonitor(monitor, 1));

            //2:
            while (true) {
                provider.ensureModels(type, new SubProgressMonitor(monitor, 1));
                try {
                    return provider.getCachedModel(this, type);
                } catch (FastOperationFailedException e) {
                    //Small chance that cache became invalidated before we could fetch model
                }
            }
        } finally {
            monitor.done();
        }
    }

    public EclipseProject getGradleModel(IProgressMonitor monitor)
            throws OperationCanceledException, CoreException {
        return getGradleModel(EclipseProject.class, monitor);
    }

    public HierarchicalEclipseProject getSkeletalGradleModel() throws FastOperationFailedException, CoreException {
        return getGradleModel(HierarchicalEclipseProject.class);
    }

    public HierarchicalEclipseProject getSkeletalGradleModel(IProgressMonitor monitor)
            throws OperationCanceledException, CoreException {
        return getGradleModel(HierarchicalEclipseProject.class, monitor);
    }

    /**
     * @return the IJavaProject instance associated with this project, or null if this project is not
     * imported in the workspace.
     */
    public IJavaProject getJavaProject() {
        //TODO: cache this?
        IProject project = getProject();
        if (project != null) {
            return JavaCore.create(project);
        }
        return null;
    }

    public void invalidateGradleModel() {
        GradleModelProvider provider = modelProvider;
        if (provider != null) {
            provider.invalidate();
        }
    }

    /**
     * Add whatever configuration stuff is expected of a Gradle project. Note that the fact that already a
     * GradleProject instance exists is no guarantee that things like required natures are already added
     * to the project. A GradleProject instance is only a kind of proxy/wrapper but its creation doesn't
     * itself enforce any kind of constraints on the underlying project.
     */
    public void convertToGradleProject(IProjectMapper projectMapping, ErrorHandler eh, IProgressMonitor monitor) {
        debug("convertToGradleProject called");
        monitor.beginTask("Convert to Gradle project", 8);
        try {
            //1: natures
            NatureUtils.ensure(getProject(), new SubProgressMonitor(monitor, 1), GradleNature.NATURE_ID, //Must be first to make gradle project icon have gradle nature showing 
                    JavaCore.NATURE_ID);
            debug("convertToGradleProject natures added");
            IJavaProject jProject = getJavaProject();

            //2: Configure class path containers for java
            ClassPath classPath = new ClassPath(this, jProject.getRawClasspath());
            classPath.ensureJREContainer(); //TODO: should be based on java preferences from gradle
            classPath.setOn(jProject, new SubProgressMonitor(monitor, 1));
            debug("convertToGradleProject JRE container added");

            //3: Refresh source folders
            refreshSourceFolders(eh, new SubProgressMonitor(monitor, 1));
            debug("refreshed source folders");

            //4: Refresh project deps
            refreshProjectDependencies(projectMapping, new SubProgressMonitor(monitor, 1));

            //5: Force root project cache to be set
            try {
                getRootProject();
            } catch (FastOperationFailedException e) {
                //Shouldn't happen... because by now, there should already be gradle model available
                ExceptionUtil.coreException(e);
            } finally {
                monitor.worked(1);
            }

            //6: Enable DSL support
            DSLDSupport.maybeAdd(this, eh, new SubProgressMonitor(monitor, 1));

            //7: Add classpath container
            GradleClassPathContainer.addTo(getJavaProject(), new SubProgressMonitor(monitor, 1));

            //8: Add WTP fixups
            WTPUtil.addWebLibraries(this);
            monitor.worked(1);
        } catch (CoreException e) {
            eh.handleError(e);
        } finally {
            monitor.done();
        }
    }

    /**
     * Get the IProject in the workspace corresponding to this GradleProject. If a corresponding project
     * doesn't exist in the workspace this methhod returns null.
     */
    public IProject getProject() {
        //TODO: This cached doesn't work well if correct value for getProject is null. But this is tricky,
        //  since 'caching' a null value is a bit dangerous. We would need to nvalidate the cached value
        //  somehow if when projects get created in the workspace.
        if (cachedProject != null && cachedProject.exists()) {
            return cachedProject;
        }
        for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
            if (project.getLocation().toFile().equals(location)) {
                this.cachedProject = project;
                return project;
            }
        }
        ;
        return null;
    }

    //   /**
    //    * Set the cached GradleModel for this project. If the overWrite flag is set to true, then the model will
    //    * be replaced no matter what. If the overwrite flag is not set, then we will only overwrite the existing model
    //    * if the newly provided model is more detailed than what we currently have.
    //    */
    //   public synchronized void setGradleModel(HierarchicalEclipseProject model, boolean overwrite) {
    //      if (overwrite) {
    //         internalSetModel(model);
    //      } else {
    //         //Be careful not to loose info by replacing our model with a less detailed model!
    //         if (this.gradleModel==null) {
    //            internalSetModel(model);
    //         } else if (isLessDetailed(model, this.gradleModel)) {
    //            //Don't replace the old model with a less detailed one.
    //         } else {
    //            internalSetModel(model);
    //         }
    //      }
    //   }

    void notifyModelListeners(HierarchicalEclipseProject newModel) {
        IGradleModelListener[] ls = modelListeners.toArray();
        for (IGradleModelListener l : ls) {
            //Iterate over local copy of listeners array to avoid concurrent modification exception (without needing to place
            //this code in synch block.
            l.modelChanged(this);
        }
    }

    //   private boolean isLessDetailed(HierarchicalEclipseProject lessModel, HierarchicalEclipseProject moreModel) {
    //      if (lessModel instanceof HierarchicalEclipseProject) {
    //         return moreModel instanceof EclipseProject;
    //      }
    //      //If we get here, lessModel can only be a EclipseProject, so it can't be less detailed than any other model
    //      return false;
    //   }

    @Override
    public String toString() {
        IProject project = getProject();
        if (project != null) {
            return "G" + project;
        }
        return "G" + location;
    }

    //   /**
    //    * For debugging purposes, dump dependency graph (as a tree) onto system out.
    //    */
    //   public void printDependencyGraph() {
    //      System.out.println(">>>>>>>> dependencies for "+getName()+">>>>>>>>>>>>>");
    //      try {
    //         printDependencyGraph(0, getGradleModel(new NullProgressMonitor()));
    //      } catch (OperationCanceledException e) {
    //         e.printStackTrace();
    //      } catch (CoreException e) {
    //         e.printStackTrace();
    //      }
    //      System.out.println("<<<<<<<< dependencies for "+getName()+"<<<<<<<<<<<<<<");
    //   }
    //
    //   private static void printDependencyGraph(int i, EclipseProject project) {
    //      for (int j = 0; j < i; j++) {
    //         System.out.print("  ");
    //      }
    //      System.out.println(GradleImportOperation.getDefaultEclipseName(project));
    //      for (EclipseProjectDependency dep : project.getProjectDependencies().getAll()) {
    //         printDependencyGraph(i+1, dep.getTargetProject());
    //      }
    //   }

    /**
     * @return Absolute file system location corresponding to this project. This is never null.
     */
    public File getLocation() {
        return location;
    }

    public static String getHierarchicalName(HierarchicalEclipseProject model) {
        HierarchicalEclipseProject parent = model.getParent();
        String name = parent == null ? "" : getHierarchicalName(parent) + ".";
        name += getName(model);
        return name;
    }

    /**
     * Use this method instead of HierachicalEclipseProject.getName() to avoid bugs like 
     * https://issuetracker.springsource.com/browse/STS-1841.
     * 
     * @param model
     * @return
     */
    public static String getName(HierarchicalEclipseProject model) {
        return model.getName().replace('/', '.');
    }

    public void removeModelListener(IGradleModelListener listener) {
        modelListeners.remove(listener);
    }

    public void addModelListener(IGradleModelListener modelListener) {
        modelListeners.add(modelListener);
    }

    /**
     * @return a set of tasks obtained from this project alone.
     */
    public DomainObjectSet<? extends GradleTask> getTasks(IProgressMonitor monitor)
            throws OperationCanceledException, CoreException {
        monitor.beginTask("Retrieve tasks for " + getDisplayName(), 1);
        try {
            EclipseProject model = getGradleModel(new SubProgressMonitor(monitor, 1));
            return GradleProject.getTasks(model);
        } finally {
            monitor.done();
        }
    }

    /**
     * @return a set of "task path" strings obtained from this project and all subprojects.
     */
    public Set<String> getAllTasks() throws FastOperationFailedException, CoreException {
        EclipseProject model = getGradleModel();
        return getAllTasks(model);
    }

    /**
     * @return a set of "task path" strings obtained from this project and all subprojects.
     */
    public Set<String> getAllTasks(IProgressMonitor mon) throws OperationCanceledException, CoreException {
        EclipseProject model = getGradleModel(mon);
        return getAllTasks(model);
    }

    private static Set<String> getAllTasks(EclipseProject model) {
        Set<String> result = new HashSet<String>();
        collectAllTasks(model, result);
        return result;
    }

    private static void collectAllTasks(EclipseProject model, Set<String> result) {
        DomainObjectSet<? extends GradleTask> tasks = getTasks(model);
        for (GradleTask t : tasks) {
            result.add(t.getPath());
        }
        DomainObjectSet<? extends EclipseProject> projects = model.getChildren();
        for (EclipseProject p : projects) {
            collectAllTasks(p, result);
        }
    }

    public static DomainObjectSet<? extends GradleTask> getTasks(EclipseProject model) {
        return model.getGradleProject().getTasks();
        //      return model.getTasks();
    }

    /**
     * Is guaranteed to return some non-null String that can be used to identigy the project 
     * to the user. Uses the Eclipse project name if available or the project location otherwise
     */
    public String getDisplayName() {
        String name = getName();
        if (name != null) {
            return name;
        }
        return getLocation().toString();
    }

    /**
     * Get the parent of this project in the Gradle project hierarchy.
     * @return parent project or null if the project has no parent.
     * @throws FastOperationFailedException
     * @throws CoreException 
     */
    public GradleProject getParent() throws FastOperationFailedException, CoreException {
        HierarchicalEclipseProject model = getSkeletalGradleModel();
        HierarchicalEclipseProject parentModel = model.getParent();
        if (parentModel != null) {
            return GradleCore.create(parentModel);
        }
        return null;
    }

    public boolean dependsOn(GradleProject other) throws FastOperationFailedException, CoreException {
        HierarchicalEclipseProject model = getSkeletalGradleModel();
        DomainObjectSet<? extends EclipseProjectDependency> deps = model.getProjectDependencies();
        for (EclipseProjectDependency _dep : deps) {
            GradleProject dep = GradleCore.create(_dep.getTargetProject());
            if (dep == other) {
                return true;
            }
        }
        return false;
    }

    /**
     * A 'conservative' version of dependsOn. If for some reason it cannot be established whether
     * this project depends on the other project (typically, because we don't have a model).
     * Then this method returns a value which is assumed to be conservatively safe in the context
     * where the method is called. 
     */
    public boolean conservativeDependsOn(GradleProject other, boolean safeValue) {
        try {
            return this == other || dependsOn(other);
        } catch (FastOperationFailedException e) {
            //Ignore... expected
        } catch (CoreException e) {
            GradleCore.log(e);
        }
        return safeValue;
    }

    /**
     * This method returns the root project associated with a given eclipse project.
     * This operation may fail (FastOperationFailedException) if a link to the root 
     * project was not persisted and there is no available model to determine the 
     * root project.
     */
    public GradleProject getRootProject() throws FastOperationFailedException {
        GradleProjectPreferences prefs = getProjectPreferences();
        if (prefs != null) {
            File rootLocation = prefs.getRootProjectLocation();
            if (rootLocation != null) {
                return GradleCore.create(rootLocation);
            }
        }
        //No project prefs, or broken prefs... Try the cached modelProvider... 
        GroupedModelProvider provider = modelProvider;
        if (provider != null) {
            GradleProject rootProject = provider.getRootProject();
            if (prefs != null) {
                //Repair project prefs
                prefs.setRootProjectLocation(rootProject.getLocation());
            }
            return rootProject;
        }
        throw new FastOperationFailedException("GradleProject '" + getDisplayName()
                + "' neither has model provider nor a persisted root project location");
    }

    /**
     * Like getRootProject, but returns null instead of throwing {@link FastOperationFailedException}
     */
    public GradleProject getRootProjectMaybe() {
        try {
            return getRootProject();
        } catch (FastOperationFailedException e) {
            return null;
        }
    }

    public void setModelProvider(GroupedModelProvider provider) {
        if (this.modelProvider != provider) {
            this.modelProvider = provider;
            GradleProjectPreferences prefs = getProjectPreferences();
            if (prefs != null) {
                //Setting the root project location will ensure we can create the model provider after workspace is restarted
                prefs.setRootProjectLocation(provider.getRootProject().getLocation());
            }
        }
    }

    public boolean isAtLeastM4() throws FastOperationFailedException, CoreException {
        HierarchicalEclipseProject model = getSkeletalGradleModel();
        try {
            model.getLinkedResources();
            return true;
        } catch (UnsupportedOperationException e) {
            //This operation was added in M4
            return false;
        }
    }

    public ClassPath getClassPath() throws JavaModelException {
        return new ClassPath(this, getJavaProject().getRawClasspath());
    }

    /**
     * Invalidate current model, if any, and then request a new model to be built asynchronously.
     */
    public void requestGradleModelRefresh() throws CoreException {
        invalidateGradleModel();
        try {
            requestGradleModel();
        } catch (FastOperationFailedException e) {
            //Expected... swallow
        }
    }

    public GradleDependencyComputer getDependencyComputer() {
        return dependencyComputer;
    }

}