diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index ff4374eb52a2..df0d5da1f7de 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -49,6 +49,7 @@ + org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper + + org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesDetector + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleLinkedProjectAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleLinkedProjectAction.java index bbd49d974a04..701da05b145b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleLinkedProjectAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleLinkedProjectAction.java @@ -2,6 +2,7 @@ package org.jetbrains.plugins.gradle.action; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +22,13 @@ public abstract class AbstractGradleLinkedProjectAction extends AnAction { @Override public void update(AnActionEvent e) { - final String path = getLinkedProjectPath(e.getDataContext()); - final boolean visible = path != null; + final Pair pair = deriveProjects(e.getDataContext()); + final boolean visible = pair != null; e.getPresentation().setVisible(visible); if (!visible) { return; } - doUpdate(e.getPresentation(), path); + doUpdate(e.getPresentation(), pair.first, pair.second); } @Override @@ -36,16 +37,16 @@ public abstract class AbstractGradleLinkedProjectAction extends AnAction { if (project == null) { return; } - final String path = getLinkedProjectPath(e.getDataContext()); - if (path == null) { + final Pair pair = deriveProjects(e.getDataContext()); + if (pair == null) { e.getPresentation().setVisible(false); return; } - doActionPerformed(project, path); + doActionPerformed(project, pair.second); } @Nullable - protected static String getLinkedProjectPath(@Nullable DataContext context) { + private static Pair deriveProjects(@Nullable DataContext context) { if (context == null) { return null; } @@ -56,9 +57,12 @@ public abstract class AbstractGradleLinkedProjectAction extends AnAction { } final String path = GradleSettings.getInstance(project).LINKED_PROJECT_FILE_PATH; - return (StringUtil.isEmpty(path) || !new File(path).isFile()) ? null : path; + if (StringUtil.isEmpty(path) || !new File(path).isFile()) { + return null; + } + return new Pair(project, path); } - protected abstract void doUpdate(@NotNull Presentation presentation, @NotNull String linkedProjectPath); + protected abstract void doUpdate(@NotNull Presentation presentation, @NotNull Project project, @NotNull String linkedProjectPath); protected abstract void doActionPerformed(@NotNull Project project, @NotNull String linkedProjectPath); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleOpenScriptAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleOpenScriptAction.java index f010e59ebde8..79f35b14803a 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleOpenScriptAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleOpenScriptAction.java @@ -1,7 +1,5 @@ package org.jetbrains.plugins.gradle.action; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -32,7 +30,7 @@ public class GradleOpenScriptAction extends AbstractGradleLinkedProjectAction im } @Override - protected void doUpdate(@NotNull Presentation presentation, @NotNull String linkedProjectPath) { + protected void doUpdate(@NotNull Presentation presentation, @NotNull Project project, @NotNull String linkedProjectPath) { } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java index 17fa3a0fbc4e..087e1706733b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java @@ -1,17 +1,16 @@ package org.jetbrains.plugins.gradle.action; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.config.GradleSettings; -import org.jetbrains.plugins.gradle.task.GradleResolveProjectTask; +import org.jetbrains.plugins.gradle.task.GradleTaskManager; +import org.jetbrains.plugins.gradle.task.GradleTaskType; import org.jetbrains.plugins.gradle.util.GradleBundle; - -import java.util.concurrent.atomic.AtomicBoolean; +import org.jetbrains.plugins.gradle.util.GradleUtil; /** * Forces the 'gradle' plugin to retrieve the most up-to-date info about the @@ -21,34 +20,25 @@ import java.util.concurrent.atomic.AtomicBoolean; * @author Denis Zhdanov * @since 1/23/12 3:48 PM */ -public class GradleRefreshProjectAction extends AbstractGradleLinkedProjectAction implements DumbAware { +public class GradleRefreshProjectAction extends AbstractGradleLinkedProjectAction implements DumbAware, AnAction.TransparentUpdate { - private final AtomicBoolean myInProgress = new AtomicBoolean(); - public GradleRefreshProjectAction() { getTemplatePresentation().setText(GradleBundle.message("gradle.action.refresh.project.text")); getTemplatePresentation().setDescription(GradleBundle.message("gradle.action.refresh.project.description")); } @Override - protected void doUpdate(@NotNull Presentation presentation, @NotNull String linkedProjectPath) { - presentation.setEnabled(!myInProgress.get()); + protected void doUpdate(@NotNull Presentation presentation, @NotNull Project project, @NotNull String linkedProjectPath) { + boolean enabled = false; + final GradleTaskManager taskManager = ServiceManager.getService(GradleTaskManager.class); + if (taskManager != null) { + enabled = !taskManager.hasTaskOfTypeInProgress(GradleTaskType.RESOLVE_PROJECT); + } + presentation.setEnabled(enabled); } @Override protected void doActionPerformed(@NotNull final Project project, @NotNull final String linkedProjectPath) { - myInProgress.set(true); - ProgressManager.getInstance().run(new Task.Backgroundable(project, GradleBundle.message("gradle.sync.progress.text")) { - @Override - public void run(@NotNull final ProgressIndicator indicator) { - try { - GradleResolveProjectTask task = new GradleResolveProjectTask(project, linkedProjectPath, true); - task.execute(indicator); - } - finally { - myInProgress.set(false); - } - } - }); + GradleUtil.refreshProject(project); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java index d8a5a8d4a29c..760166cb0194 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java @@ -1,14 +1,10 @@ package org.jetbrains.plugins.gradle.importing; -import com.intellij.execution.rmi.RemoteUtil; import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.projectWizard.WizardContext; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.projectRoots.JavaSdk; @@ -17,7 +13,6 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ui.configuration.ModulesProvider; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.packaging.artifacts.ModifiableArtifactModel; import com.intellij.projectImport.ProjectImportBuilder; import org.jetbrains.annotations.NotNull; @@ -26,11 +21,9 @@ import org.jetbrains.plugins.gradle.config.GradleSettings; import org.jetbrains.plugins.gradle.model.GradleEntity; import org.jetbrains.plugins.gradle.model.GradleModule; import org.jetbrains.plugins.gradle.model.GradleProject; -import org.jetbrains.plugins.gradle.remote.GradleApiException; -import org.jetbrains.plugins.gradle.task.GradleResolveProjectTask; import org.jetbrains.plugins.gradle.ui.GradleIcons; import org.jetbrains.plugins.gradle.util.GradleBundle; -import org.jetbrains.plugins.gradle.util.GradleLog; +import org.jetbrains.plugins.gradle.util.GradleUtil; import javax.swing.*; import java.io.File; @@ -156,38 +149,7 @@ public class GradleProjectImportBuilder extends ProjectImportBuilder errorReason = new Ref(); try { final Project project = getProject(wizardContext); - ProgressManager.getInstance().run(new Task.Modal(project, GradleBundle.message("gradle.import.progress.text"), true) { - @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - @Override - public void run(@NotNull final ProgressIndicator indicator) { - GradleResolveProjectTask task = new GradleResolveProjectTask(project, myProjectFile.getAbsolutePath(), false); - task.execute(indicator); - myGradleProject = task.getProject(); - if (myGradleProject != null) { - return; - } - final Throwable error = task.getError(); - if (error == null) { - return; - } - Throwable unwrapped = RemoteUtil.unwrap(error); - String reason = unwrapped.getLocalizedMessage(); - if (!StringUtil.isEmpty(reason)) { - errorReason.set(reason); - } - if (unwrapped.getClass() == NoClassDefFoundError.class) { - errorReason.set(GradleBundle.message("gradle.import.text.incomplete.tooling.api")); - } - else if (unwrapped.getClass() == GradleApiException.class) { - GradleLog.LOG.warn("Can't resolve gradle project. Reason: gradle api threw an exception:\n" - + ((GradleApiException)unwrapped).getOriginalReason() - ); - } - else { - GradleLog.LOG.warn("Can't resolve gradle project", unwrapped); - } - } - }); + myGradleProject = GradleUtil.refreshProject(project, myProjectFile.getAbsolutePath(), errorReason, false, true); } catch (IllegalArgumentException e) { throw new ConfigurationException(e.getMessage(), GradleBundle.message("gradle.import.text.error.cannot.parse.project")); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManager.java index 7c134a94f968..a048f1cead9f 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManager.java @@ -9,6 +9,15 @@ import org.jetbrains.plugins.gradle.task.GradleTaskId; */ public interface GradleProgressNotificationManager { + /** + * Allows to register given listener to listen events from all tasks. + * + * @param listener listener to register + * @return true if given listener was not registered before for the given key; + * false otherwise + */ + boolean addNotificationListener(@NotNull GradleTaskNotificationListener listener); + /** * Allows to register given listener within the current manager for listening events from the task with the target id. * diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManagerImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManagerImpl.java index 80787d206f06..0eb9d5570ce4 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManagerImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManagerImpl.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.remote.RemoteGradleProgressNotificationManager; import org.jetbrains.plugins.gradle.task.GradleTaskId; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; @@ -19,9 +20,15 @@ public class GradleProgressNotificationManagerImpl extends RemoteObject implements GradleProgressNotificationManager, RemoteGradleProgressNotificationManager { - private final ConcurrentMap> myListeners + private final ConcurrentMap/* EMPTY_SET as a sign of 'all ids' */> myListeners = new ConcurrentHashMap>(); + @Override + public boolean addNotificationListener(@NotNull GradleTaskNotificationListener listener) { + Set dummy = Collections.emptySet(); + return myListeners.put(listener, dummy) == null; + } + @Override public boolean addNotificationListener(@NotNull GradleTaskId taskId, @NotNull GradleTaskNotificationListener listener) { Set ids = null; @@ -41,10 +48,22 @@ public class GradleProgressNotificationManagerImpl extends RemoteObject return myListeners.remove(listener) != null; } + @Override + public void onQueued(@NotNull GradleTaskId id) { + for (Map.Entry> entry : myListeners.entrySet()) { + final Set ids = entry.getValue(); + if (Collections.EMPTY_SET == ids || ids.contains(id)) { + entry.getKey().onQueued(id); + } + } + + } + @Override public void onStart(@NotNull GradleTaskId id) { for (Map.Entry> entry : myListeners.entrySet()) { - if (entry.getValue().contains(id)) { + final Set ids = entry.getValue(); + if (Collections.EMPTY_SET == ids || ids.contains(id)) { entry.getKey().onStart(id); } } @@ -53,7 +72,8 @@ public class GradleProgressNotificationManagerImpl extends RemoteObject @Override public void onStatusChange(@NotNull GradleTaskNotificationEvent event) { for (Map.Entry> entry : myListeners.entrySet()) { - if (entry.getValue().contains(event.getId())) { + final Set ids = entry.getValue(); + if (Collections.EMPTY_SET == ids || ids.contains(event.getId())) { entry.getKey().onStatusChange(event); } } @@ -62,7 +82,8 @@ public class GradleProgressNotificationManagerImpl extends RemoteObject @Override public void onEnd(@NotNull GradleTaskId id) { for (Map.Entry> entry : myListeners.entrySet()) { - if (entry.getValue().contains(id)) { + final Set ids = entry.getValue(); + if (Collections.EMPTY_SET == ids || ids.contains(id)) { entry.getKey().onEnd(id); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListener.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListener.java index 1b4bda67e8ec..ba4a73fee1fa 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListener.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListener.java @@ -11,6 +11,16 @@ import org.jetbrains.plugins.gradle.task.GradleTaskId; */ public interface GradleTaskNotificationListener { + /** + * Notifies that task with the given id is queued for the execution. + *

+ * 'Queued' here means that intellij process-local codebase receives request to execute the target task and even has not been + * sent it to the slave gradle api process. + * + * @param id target task's id + */ + void onQueued(@NotNull GradleTaskId id); + /** * Notifies that task with the given id is about to be started. * diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListenerAdapter.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListenerAdapter.java index a5ff4965a90d..c2ee8ee511b9 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListenerAdapter.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListenerAdapter.java @@ -8,6 +8,11 @@ import org.jetbrains.plugins.gradle.task.GradleTaskId; * @since 11/10/11 12:18 PM */ public class GradleTaskNotificationListenerAdapter implements GradleTaskNotificationListener { + + @Override + public void onQueued(@NotNull GradleTaskId id) { + } + @Override public void onStart(@NotNull GradleTaskId id) { } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java index d2d5dc0e3072..2955f9e3961c 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java @@ -1,12 +1,10 @@ package org.jetbrains.plugins.gradle.remote; import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.gradle.task.GradleTaskId; -import org.jetbrains.plugins.gradle.task.GradleTaskType; +import org.jetbrains.plugins.gradle.task.GradleTaskAware; import java.rmi.Remote; import java.rmi.RemoteException; -import java.util.Collection; /** * Serves as a facade for working with @@ -18,7 +16,7 @@ import java.util.Collection; * @author Denis Zhdanov * @since 8/8/11 10:52 AM */ -public interface GradleApiFacade extends Remote { +public interface GradleApiFacade extends Remote, GradleTaskAware { /** * Exposes 'resolve gradle project' service that works at another process. @@ -45,23 +43,4 @@ public interface GradleApiFacade extends Remote { * @throws RemoteException in case of unexpected I/O exception during processing */ void applyProgressManager(@NotNull RemoteGradleProgressNotificationManager progressManager) throws RemoteException; - - /** - * Asks remote gradle process to check if a task with the given id is being executed right now. - * - * @param id target task's id - * @return true if a task with the given id is executed at the moment; false otherwise - * @throws RemoteException in case of unexpected I/O exception during processing - */ - boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException; - - /** - * Allows to ask remote gradle process for the ids of the tasks with the given type being executed now. - * - * @param type target task type - * @return ids of the tasks of the target type being executed at the moment (if any) - * @throws RemoteException in case of unexpected I/O exception during processing - */ - @NotNull - Collection getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java index 02a42690d19f..8972c627d369 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java @@ -31,7 +31,9 @@ import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManager; +import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl; import org.jetbrains.plugins.gradle.remote.impl.GradleApiFacadeImpl; +import org.jetbrains.plugins.gradle.remote.wrapper.GradleApiFacadeWrapper; import org.jetbrains.plugins.gradle.util.GradleBundle; import org.jetbrains.plugins.gradle.util.GradleLibraryManager; import org.jetbrains.plugins.gradle.util.GradleLog; @@ -69,8 +71,8 @@ public class GradleApiFacadeManager { private final AtomicReference myExportedProgressManager = new AtomicReference(); - private final GradleLibraryManager myGradleLibraryManager; - private final RemoteGradleProgressNotificationManager myProgressManager; + @NotNull private final GradleLibraryManager myGradleLibraryManager; + @NotNull private final GradleProgressNotificationManagerImpl myProgressManager; // Please note that we don't use RemoteGradleProcessSettings as the 'Configuration' type parameter here because we need // to apply the settings to the newly created process. I.e. every time new process is created we need to call @@ -80,7 +82,7 @@ public class GradleApiFacadeManager { public GradleApiFacadeManager(@NotNull GradleLibraryManager gradleLibraryManager, @NotNull GradleProgressNotificationManager manager) { myGradleLibraryManager = gradleLibraryManager; - myProgressManager = (RemoteGradleProgressNotificationManager)manager; + myProgressManager = (GradleProgressNotificationManagerImpl)manager; mySupport = new RemoteProcessSupport(GradleApiFacade.class) { @Override protected void fireModificationCountChanged() { @@ -223,10 +225,11 @@ public class GradleApiFacadeManager { myFacade.compareAndSet(pair, null); } - GradleApiFacade result = mySupport.acquire(this, ""); - if (result == null) { + final GradleApiFacade facade = mySupport.acquire(this, ""); + if (facade == null) { throw new IllegalStateException("Can't obtain facade to working with gradle api at the remote process"); } + final GradleApiFacade result = new GradleApiFacadeWrapper(facade, myProgressManager); Pair newPair = new Pair(result, getRemoteSettings()); if (!myFacade.compareAndSet(null, newPair)) { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProgressNotificationManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProgressNotificationManager.java index 22b87a335e12..dc35e524c44d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProgressNotificationManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProgressNotificationManager.java @@ -18,6 +18,9 @@ import java.rmi.RemoteException; public interface RemoteGradleProgressNotificationManager extends Remote { RemoteGradleProgressNotificationManager NULL_OBJECT = new RemoteGradleProgressNotificationManager() { + @Override + public void onQueued(@NotNull GradleTaskId id) throws RemoteException { + } @Override public void onStart(@NotNull GradleTaskId id) { } @@ -28,7 +31,9 @@ public interface RemoteGradleProgressNotificationManager extends Remote { public void onEnd(@NotNull GradleTaskId id) { } }; - + + void onQueued(@NotNull GradleTaskId id) throws RemoteException; + void onStart(@NotNull GradleTaskId id) throws RemoteException; void onStatusChange(@NotNull GradleTaskNotificationEvent event) throws RemoteException; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java index 52d709b0cd7f..84af289eec64 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java @@ -2,12 +2,10 @@ package org.jetbrains.plugins.gradle.remote; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationListener; -import org.jetbrains.plugins.gradle.task.GradleTaskId; -import org.jetbrains.plugins.gradle.task.GradleTaskType; +import org.jetbrains.plugins.gradle.task.GradleTaskAware; import java.rmi.Remote; import java.rmi.RemoteException; -import java.util.Collection; /** * Generic interface with common functionality for all remote services that work with gradle tooling api. @@ -15,28 +13,8 @@ import java.util.Collection; * @author Denis Zhdanov * @since 8/9/11 3:19 PM */ -public interface RemoteGradleService extends Remote { +public interface RemoteGradleService extends Remote, GradleTaskAware { - /** - * Allows to check if current service executes the target task. - * - * @param id target task's id - * @return true if a task with the given id is executed at the moment by the current service; - * false otherwise - * @throws RemoteException as required by RMI - */ - boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException; - - /** - * Allows to ask current service for the ids of the tasks with the given type being executed now. - * - * @param type target task type - * @return ids of the tasks of the target type being executed at the moment by the current service (if any) - * @throws RemoteException as required by RMI - */ - @NotNull - Collection getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException; - /** * Provides the service settings to use. * diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleApiFacadeImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleApiFacadeImpl.java index 82e74734ff4e..74ed43dd9c74 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleApiFacadeImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleApiFacadeImpl.java @@ -142,21 +142,30 @@ public class GradleApiFacadeImpl extends RemoteServer implements GradleApiFacade @NotNull @Override - public Collection getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException { - Set result = null; + public Map> getTasksInProgress() throws RemoteException { + Map> result = null; for (RemoteGradleService service : myRemotes.values()) { - final Collection tasks = service.getTasksInProgress(type); + final Map> tasks = service.getTasksInProgress(); if (tasks.isEmpty()) { continue; } if (result == null) { - result = new HashSet(); + result = new HashMap>(); + } + for (Map.Entry> entry : tasks.entrySet()) { + Set ids = result.get(entry.getKey()); + if (ids == null) { + result.put(entry.getKey(), ids = new HashSet()); + } + ids.addAll(entry.getValue()); } - result.addAll(tasks); } - return result == null ? Collections.emptySet() : result; + if (result == null) { + result = Collections.emptyMap(); + } + return result; } - + @Override public void applySettings(@NotNull RemoteGradleProcessSettings settings) throws RemoteException { mySettings.set(settings); @@ -208,6 +217,10 @@ public class GradleApiFacadeImpl extends RemoteServer implements GradleApiFacade myManager = manager; } + @Override + public void onQueued(@NotNull GradleTaskId id) { + } + @Override public void onStart(@NotNull GradleTaskId id) { try { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java index 7bd336a2a324..9486e30536e8 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java @@ -81,11 +81,10 @@ public class GradleProjectResolverImpl extends RemoteObject implements GradlePro @NotNull @Override - public Collection getTasksInProgress(@NotNull GradleTaskType type) { - if (type != GradleTaskType.RESOLVE_PROJECT || myTasksInProgress.isEmpty()) { - return Collections.emptySet(); - } - return new HashSet(myTasksInProgress); + public Map> getTasksInProgress() throws RemoteException { + Map> result = new HashMap>(); + result.put(GradleTaskType.RESOLVE_PROJECT, new HashSet(myTasksInProgress)); + return result; } @NotNull diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleApiFacadeWrapper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleApiFacadeWrapper.java new file mode 100644 index 000000000000..9838e8320b21 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleApiFacadeWrapper.java @@ -0,0 +1,62 @@ +package org.jetbrains.plugins.gradle.remote.wrapper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl; +import org.jetbrains.plugins.gradle.remote.GradleApiFacade; +import org.jetbrains.plugins.gradle.remote.GradleProjectResolver; +import org.jetbrains.plugins.gradle.remote.RemoteGradleProcessSettings; +import org.jetbrains.plugins.gradle.remote.RemoteGradleProgressNotificationManager; +import org.jetbrains.plugins.gradle.task.GradleTaskId; +import org.jetbrains.plugins.gradle.task.GradleTaskType; + +import java.rmi.RemoteException; +import java.util.Map; +import java.util.Set; + +/** + * This class acts as a point where target remote gradle services are proxied. + *

+ * Check service wrapper contracts for more details. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 2/8/12 7:21 PM + */ +public class GradleApiFacadeWrapper implements GradleApiFacade { + + @NotNull private final GradleApiFacade myDelegate; + @NotNull private final GradleProgressNotificationManagerImpl myNotificationManager; + + public GradleApiFacadeWrapper(@NotNull GradleApiFacade delegate, @NotNull GradleProgressNotificationManagerImpl notificationManager) { + myDelegate = delegate; + myNotificationManager = notificationManager; + } + + @NotNull + @Override + public GradleProjectResolver getResolver() throws RemoteException, IllegalStateException { + return new GradleProjectResolverWrapper(myDelegate.getResolver(), myNotificationManager); + } + + @Override + public void applySettings(@NotNull RemoteGradleProcessSettings settings) throws RemoteException { + myDelegate.applySettings(settings); + } + + @Override + public void applyProgressManager(@NotNull RemoteGradleProgressNotificationManager progressManager) throws RemoteException { + myDelegate.applyProgressManager(progressManager); + } + + @Override + public boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException { + return myDelegate.isTaskInProgress(id); + } + + @NotNull + @Override + public Map> getTasksInProgress() throws RemoteException { + return myDelegate.getTasksInProgress(); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleProjectResolverWrapper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleProjectResolverWrapper.java new file mode 100644 index 000000000000..b31f37f7edc0 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleProjectResolverWrapper.java @@ -0,0 +1,68 @@ +package org.jetbrains.plugins.gradle.remote.wrapper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.model.GradleProject; +import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl; +import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationListener; +import org.jetbrains.plugins.gradle.remote.GradleApiException; +import org.jetbrains.plugins.gradle.remote.GradleProjectResolver; +import org.jetbrains.plugins.gradle.remote.RemoteGradleProcessSettings; +import org.jetbrains.plugins.gradle.task.GradleTaskId; +import org.jetbrains.plugins.gradle.task.GradleTaskManager; +import org.jetbrains.plugins.gradle.task.GradleTaskType; + +import java.rmi.RemoteException; +import java.util.Map; +import java.util.Set; + +/** + * Intercepts calls to the target {@link GradleProjectResolver} and + * {@link GradleTaskManager#onQueued(GradleTaskId) updates 'queued' task status}. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 2/8/12 7:21 PM + */ +public class GradleProjectResolverWrapper implements GradleProjectResolver { + + @NotNull private final GradleProjectResolver myResolver; + @NotNull private final GradleProgressNotificationManagerImpl myNotificationManager; + + public GradleProjectResolverWrapper(@NotNull GradleProjectResolver resolver, + @NotNull GradleProgressNotificationManagerImpl notificationManager) + { + myResolver = resolver; + myNotificationManager = notificationManager; + } + + @Override + @NotNull + public GradleProject resolveProjectInfo(@NotNull GradleTaskId id, @NotNull String projectPath, boolean downloadLibraries) + throws RemoteException, GradleApiException, IllegalArgumentException, IllegalStateException + { + myNotificationManager.onQueued(id); + return myResolver.resolveProjectInfo(id, projectPath, downloadLibraries); + } + + @Override + public void setSettings(@NotNull RemoteGradleProcessSettings settings) throws RemoteException { + myResolver.setSettings(settings); + } + + @Override + public void setNotificationListener(@NotNull GradleTaskNotificationListener notificationListener) throws RemoteException { + myResolver.setNotificationListener(notificationListener); + } + + @Override + public boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException { + return myResolver.isTaskInProgress(id); + } + + @Override + @NotNull + public Map> getTasksInProgress() throws RemoteException { + return myResolver.getTasksInProgress(); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java index 05ef3cadf545..e46258ad1597 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java @@ -1,10 +1,68 @@ package org.jetbrains.plugins.gradle.sync; +import com.intellij.ProjectTopics; +import com.intellij.openapi.components.AbstractProjectComponent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootEvent; +import com.intellij.openapi.roots.ModuleRootListener; +import com.intellij.util.Alarm; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.GradleUtil; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + /** - * // TODO den add doc + * Detects project structure changes and triggers linked gradle project update. * * @author Denis Zhdanov * @since 11/3/11 3:57 PM */ -public class GradleProjectStructureChangesDetector { +public class GradleProjectStructureChangesDetector extends AbstractProjectComponent { + + private static final int REFRESH_DELAY_MILLIS = (int)TimeUnit.SECONDS.toMillis(2); + + private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + private final AtomicLong myStartRefreshTime = new AtomicLong(); + private final RefreshRequest myRequest = new RefreshRequest(); + + public GradleProjectStructureChangesDetector(@NotNull Project project) { + super(project); + subscribeToRootChanges(project); + } + + private void subscribeToRootChanges(@NotNull Project project) { + project.getMessageBus().connect(project).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + @Override + public void beforeRootsChange(ModuleRootEvent event) { + } + + @Override + public void rootsChanged(ModuleRootEvent event) { + scheduleUpdate(); + } + }); + } + + private void scheduleUpdate() { + myStartRefreshTime.set(System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + myAlarm.cancelAllRequests(); + myAlarm.addRequest(myRequest, REFRESH_DELAY_MILLIS + 16); + } + + private class RefreshRequest implements Runnable { + @Override + public void run() { + if (!myProject.isInitialized()) { + return; + } + myAlarm.cancelAllRequests(); + final long diff = System.currentTimeMillis() - myStartRefreshTime.get(); + if (diff < 0) { + myAlarm.addRequest(this, (int)-diff); + return; + } + GradleUtil.refreshProject(myProject); + } + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java index bfbf74f5d633..535ab6660ea1 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java @@ -96,7 +96,7 @@ public abstract class AbstractGradleTask implements GradleTask { } finally { for (GradleTaskNotificationListener listener : listeners) { - progressManager.addNotificationListener(getId(), listener); + progressManager.removeNotificationListener(listener); } } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskAware.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskAware.java new file mode 100644 index 000000000000..6ca2482310d4 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskAware.java @@ -0,0 +1,35 @@ +package org.jetbrains.plugins.gradle.task; + +import org.jetbrains.annotations.NotNull; + +import java.rmi.RemoteException; +import java.util.Map; +import java.util.Set; + +/** + * Represents service that exposes information about the tasks being processed. + * + * @author Denis Zhdanov + * @since 2/8/12 1:46 PM + */ +public interface GradleTaskAware { + + /** + * Allows to check if current service executes the target task. + * + * @param id target task's id + * @return true if a task with the given id is executed at the moment by the current service; + * false otherwise + * @throws RemoteException as required by RMI + */ + boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException; + + /** + * Allows to ask current service for all tasks being executed at the moment. + * + * @return ids of all tasks being executed at the moment grouped by type + * @throws RemoteException as required by RMI + */ + @NotNull + Map> getTasksInProgress() throws RemoteException; +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskManager.java new file mode 100644 index 000000000000..86ae3f72762d --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskManager.java @@ -0,0 +1,121 @@ +package org.jetbrains.plugins.gradle.task; + +import com.intellij.util.Alarm; +import com.intellij.util.containers.ConcurrentHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManager; +import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationEvent; +import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationListener; +import org.jetbrains.plugins.gradle.remote.GradleApiFacadeManager; +import org.jetbrains.plugins.gradle.util.GradleLog; +import org.jetbrains.plugins.gradle.util.GradleUtil; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +/** + * Provides gradle tasks monitoring and management facilities. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 2/8/12 1:52 PM + */ +public class GradleTaskManager implements GradleTaskNotificationListener { + + /** + * We receive information about the tasks being enqueued to the slave gradle projects here. However, there is a possible + * situation when particular task has been sent to execution but remote side has not been responding for a while. There at least + * two possible explanations then: + *

+   * 
    + *
  • The task is still in progress (e.g. great number of libraries is being downloaded);
  • + *
  • Remote side has fallen (uncaught exception; manual slave gradle process kill etc);
  • + *
+ *
+ * We need to distinguish between them, so, we perform 'task pings' if any task is executed too long. Current constant holds + * criteria of 'too long execution'. + */ + private static final long REFRESH_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(10); + private static final int DETECT_HANGED_TASKS_FREQUENCY_MILLIS = (int)TimeUnit.SECONDS.toMillis(5); + + @NotNull private final ConcurrentMap myTasksInProgress = new ConcurrentHashMap(); + @NotNull private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + + @NotNull private final GradleApiFacadeManager myFacadeManager; + + public GradleTaskManager(@NotNull GradleApiFacadeManager facadeManager, @NotNull GradleProgressNotificationManager notificationManager) { + myFacadeManager = facadeManager; + notificationManager.addNotificationListener(this); + myAlarm.addRequest(new Runnable() { + @Override + public void run() { + try { + for (Long limit : myTasksInProgress.values()) { + if (limit <= System.currentTimeMillis()) { + update(); + break; + } + } + } + finally { + myAlarm.cancelAllRequests(); + myAlarm.addRequest(this, DETECT_HANGED_TASKS_FREQUENCY_MILLIS); + } + } + }, DETECT_HANGED_TASKS_FREQUENCY_MILLIS); + } + + /** + * Allows to check if any task of the given type is being executed at the moment. + * + * @param type target task type + * @return true if any task of the given type is being executed at the moment; + * false otherwise + */ + public boolean hasTaskOfTypeInProgress(@NotNull GradleTaskType type) { + for (GradleTaskId id : myTasksInProgress.keySet()) { + if (type.equals(id.getType())) { + return true; + } + } + return false; + } + + @Override + public void onQueued(@NotNull GradleTaskId id) { + myTasksInProgress.put(id, System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + } + + @Override + public void onStart(@NotNull GradleTaskId id) { + myTasksInProgress.put(id, System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + } + + @Override + public void onStatusChange(@NotNull GradleTaskNotificationEvent event) { + myTasksInProgress.put(event.getId(), System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + } + + @Override + public void onEnd(@NotNull GradleTaskId id) { + myTasksInProgress.remove(id); + } + + public void update() { + try { + final Map> currentState = myFacadeManager.getFacade().getTasksInProgress(); + myTasksInProgress.clear(); + for (Set ids : currentState.values()) { + for (GradleTaskId id : ids) { + myTasksInProgress.put(id, System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + } + } + } + catch (Exception e) { + GradleLog.LOG.warn(String.format("Can't refresh active tasks. Reason: %s", GradleUtil.buildErrorMessage(e))); + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java index a285e2a27325..d1da29e53b4d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java @@ -1,21 +1,36 @@ package org.jetbrains.plugins.gradle.util; +import com.intellij.execution.rmi.RemoteUtil; import com.intellij.ide.actions.OpenProjectFileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileTypeDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.BalloonBuilder; import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.PathUtil; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.config.GradleSettings; +import org.jetbrains.plugins.gradle.model.GradleProject; +import org.jetbrains.plugins.gradle.remote.GradleApiException; +import org.jetbrains.plugins.gradle.task.GradleResolveProjectTask; import org.jetbrains.plugins.gradle.ui.GradleIcons; import javax.swing.*; import java.awt.*; import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.concurrent.TimeUnit; /** @@ -26,27 +41,6 @@ import java.util.concurrent.TimeUnit; */ public class GradleUtil { - /** - * We use this class in order to avoid static initialisation of the wrapped object - it loads number of pico container-based - * dependencies that are unavailable to the slave gradle project, so, we don't want to get unexpected NPE there. - */ - private static class DescriptorHolder { - public static final FileChooserDescriptor GRADLE_BUILD_FILE_CHOOSER_DESCRIPTOR = new OpenProjectFileChooserDescriptor(true) { - @Override - public boolean isFileSelectable(VirtualFile file) { - return GradleConstants.DEFAULT_SCRIPT_NAME.equals(file.getName()); - } - - @Override - public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { - if (!super.isFileVisible(file, showHiddenFiles)) { - return false; - } - return file.isDirectory() || GradleConstants.EXTENSION.equals(file.getExtension()); - } - }; - } - private GradleUtil() { } @@ -96,4 +90,133 @@ public class GradleUtil { } balloon.show(new RelativePoint(component, new Point(x, y)), position); } + + /** + * Delegates to the {@link #refreshProject(Project, String, Ref, boolean, boolean)} with the following defaults: + *
+   * 
    + *
  • target gradle project path is retrieved from the {@link GradleSettings gradle settings} associated with the given project;
  • + *
  • refresh process is run in background;
  • + *
  • any problem occurred during the refresh is reported to the {@link GradleLog#LOG};
  • + *
+ *
+ * + * @param project target intellij project to use + */ + public static void refreshProject(@NotNull Project project) { + final GradleSettings settings = GradleSettings.getInstance(project); + final String linkedProjectPath = settings.LINKED_PROJECT_FILE_PATH; + if (StringUtil.isEmpty(linkedProjectPath)) { + return; + } + Ref errorHolder = new Ref(); + refreshProject(project, linkedProjectPath, errorHolder, true, false); + final String error = errorHolder.get(); + if (!StringUtil.isEmpty(error)) { + GradleLog.LOG.warn(error); + } + } + + /** + * {@link RemoteUtil#unwrap(Throwable) unwraps} given exception if possible and builds error message for it. + * + * @param e exception to process + * @return error message for the given exception + */ + @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "IOResourceOpenedButNotSafelyClosed"}) + public static String buildErrorMessage(@NotNull Throwable e) { + Throwable unwrapped = RemoteUtil.unwrap(e); + String reason = unwrapped.getLocalizedMessage(); + if (!StringUtil.isEmpty(reason)) { + return reason; + } + else if (unwrapped.getClass() == GradleApiException.class) { + return String.format("gradle api threw an exception: %s", ((GradleApiException)unwrapped).getOriginalReason()); + } + else { + StringWriter writer = new StringWriter(); + unwrapped.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + } + /** + * Queries slave gradle process to refresh target gradle project. + * + * @param project target intellij project to use + * @param gradleProjectPath path of the target gradle project's file + * @param errorHolder holder for the error message that describes a problem occurred during the refresh (if any) + * @param resolveLibraries flag that identifies whether gradle libraries should be resolved during the refresh + * @return the most up-to-date gradle project (if any) + */ + @Nullable + public static GradleProject refreshProject(@NotNull final Project project, + @NotNull final String gradleProjectPath, + @NotNull final Ref errorHolder, + final boolean resolveLibraries, + final boolean modal) + { + final Ref gradleProject = new Ref(); + final TaskUnderProgress task = new TaskUnderProgress() { + @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "IOResourceOpenedButNotSafelyClosed"}) + @Override + public void execute(@NotNull ProgressIndicator indicator) { + GradleResolveProjectTask task = new GradleResolveProjectTask(project, gradleProjectPath, resolveLibraries); + task.execute(indicator); + gradleProject.set(task.getProject()); + final Throwable error = task.getError(); + if (error == null) { + return; + } + final String message = buildErrorMessage(error); + errorHolder.set(String.format("Can't resolve gradle project at '%s'. Reason: %s", gradleProjectPath, message)); + } + }; + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + if (modal) { + ProgressManager.getInstance().run(new Task.Modal(project, GradleBundle.message("gradle.import.progress.text"), true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + task.execute(indicator); + } + }); + } + else { + ProgressManager.getInstance().run(new Task.Backgroundable(project, GradleBundle.message("gradle.sync.progress.text")) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + task.execute(indicator); + } + }); + } + } + }); + return gradleProject.get(); + } + + private interface TaskUnderProgress { + void execute(@NotNull ProgressIndicator indicator); + } + + /** + * We use this class in order to avoid static initialisation of the wrapped object - it loads number of pico container-based + * dependencies that are unavailable to the slave gradle project, so, we don't want to get unexpected NPE there. + */ + private static class DescriptorHolder { + public static final FileChooserDescriptor GRADLE_BUILD_FILE_CHOOSER_DESCRIPTOR = new OpenProjectFileChooserDescriptor(true) { + @Override + public boolean isFileSelectable(VirtualFile file) { + return GradleConstants.DEFAULT_SCRIPT_NAME.equals(file.getName()); + } + + @Override + public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { + if (!super.isFileVisible(file, showHiddenFiles)) { + return false; + } + return file.isDirectory() || GradleConstants.EXTENSION.equals(file.getExtension()); + } + }; + } }