IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes

1. Refreshing gradle project on intellij module root change;
2. 'Refresh gradle action' is disabled if there is a refresh in progress;
3. Gradle tasks have 'queued' status now;
This commit is contained in:
Denis.Zhdanov
2012-02-09 19:07:55 +04:00
parent 5acb3713b5
commit 75a73d76da
22 changed files with 616 additions and 169 deletions
+4
View File
@@ -49,6 +49,7 @@
<!--Generic application services-->
<applicationService serviceImplementation="org.jetbrains.plugins.gradle.remote.GradleApiFacadeManager"/>
<applicationService serviceImplementation="org.jetbrains.plugins.gradle.util.GradleLibraryManager"/>
<applicationService serviceImplementation="org.jetbrains.plugins.gradle.task.GradleTaskManager"/>
<applicationService serviceInterface="org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManager"
serviceImplementation="org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl"/>
<applicationService serviceInterface="org.jetbrains.plugins.gradle.diff.PlatformFacade"
@@ -74,6 +75,9 @@
<component>
<implementation-class>org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesDetector</implementation-class>
</component>
</project-components>
<actions>
@@ -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<Project, String> 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<Project, String> 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<Project, String> 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, String>(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);
}
@@ -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
@@ -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);
}
}
@@ -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<GradleProje
final Ref<String> errorReason = new Ref<String>();
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"));
@@ -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 <code>true</code> if given listener was not registered before for the given key;
* <code>false</code> 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.
*
@@ -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<GradleTaskNotificationListener, Set<GradleTaskId>> myListeners
private final ConcurrentMap<GradleTaskNotificationListener, Set<GradleTaskId>/* EMPTY_SET as a sign of 'all ids' */> myListeners
= new ConcurrentHashMap<GradleTaskNotificationListener, Set<GradleTaskId>>();
@Override
public boolean addNotificationListener(@NotNull GradleTaskNotificationListener listener) {
Set<GradleTaskId> dummy = Collections.emptySet();
return myListeners.put(listener, dummy) == null;
}
@Override
public boolean addNotificationListener(@NotNull GradleTaskId taskId, @NotNull GradleTaskNotificationListener listener) {
Set<GradleTaskId> 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<GradleTaskNotificationListener, Set<GradleTaskId>> entry : myListeners.entrySet()) {
final Set<GradleTaskId> 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<GradleTaskNotificationListener, Set<GradleTaskId>> entry : myListeners.entrySet()) {
if (entry.getValue().contains(id)) {
final Set<GradleTaskId> 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<GradleTaskNotificationListener, Set<GradleTaskId>> entry : myListeners.entrySet()) {
if (entry.getValue().contains(event.getId())) {
final Set<GradleTaskId> 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<GradleTaskNotificationListener, Set<GradleTaskId>> entry : myListeners.entrySet()) {
if (entry.getValue().contains(id)) {
final Set<GradleTaskId> ids = entry.getValue();
if (Collections.EMPTY_SET == ids || ids.contains(id)) {
entry.getKey().onEnd(id);
}
}
@@ -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.
* <p/>
* '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.
*
@@ -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) {
}
@@ -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 <code>'resolve gradle project'</code> 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 <code>true</code> if a task with the given id is executed at the moment; <code>false</code> 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<GradleTaskId> getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException;
}
@@ -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<RemoteGradleProgressNotificationManager> myExportedProgressManager
= new AtomicReference<RemoteGradleProgressNotificationManager>();
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<Object, GradleApiFacade, Object>(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<GradleApiFacade, RemoteGradleProcessSettings> newPair
= new Pair<GradleApiFacade, RemoteGradleProcessSettings>(result, getRemoteSettings());
if (!myFacade.compareAndSet(null, newPair)) {
@@ -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;
@@ -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 <code>true</code> if a task with the given id is executed at the moment by the current service;
* <code>false</code> 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<GradleTaskId> getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException;
/**
* Provides the service settings to use.
*
@@ -142,21 +142,30 @@ public class GradleApiFacadeImpl extends RemoteServer implements GradleApiFacade
@NotNull
@Override
public Collection<GradleTaskId> getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException {
Set<GradleTaskId> result = null;
public Map<GradleTaskType, Set<GradleTaskId>> getTasksInProgress() throws RemoteException {
Map<GradleTaskType, Set<GradleTaskId>> result = null;
for (RemoteGradleService service : myRemotes.values()) {
final Collection<GradleTaskId> tasks = service.getTasksInProgress(type);
final Map<GradleTaskType, Set<GradleTaskId>> tasks = service.getTasksInProgress();
if (tasks.isEmpty()) {
continue;
}
if (result == null) {
result = new HashSet<GradleTaskId>();
result = new HashMap<GradleTaskType, Set<GradleTaskId>>();
}
for (Map.Entry<GradleTaskType, Set<GradleTaskId>> entry : tasks.entrySet()) {
Set<GradleTaskId> ids = result.get(entry.getKey());
if (ids == null) {
result.put(entry.getKey(), ids = new HashSet<GradleTaskId>());
}
ids.addAll(entry.getValue());
}
result.addAll(tasks);
}
return result == null ? Collections.<GradleTaskId>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 {
@@ -81,11 +81,10 @@ public class GradleProjectResolverImpl extends RemoteObject implements GradlePro
@NotNull
@Override
public Collection<GradleTaskId> getTasksInProgress(@NotNull GradleTaskType type) {
if (type != GradleTaskType.RESOLVE_PROJECT || myTasksInProgress.isEmpty()) {
return Collections.emptySet();
}
return new HashSet<GradleTaskId>(myTasksInProgress);
public Map<GradleTaskType, Set<GradleTaskId>> getTasksInProgress() throws RemoteException {
Map<GradleTaskType, Set<GradleTaskId>> result = new HashMap<GradleTaskType, Set<GradleTaskId>>();
result.put(GradleTaskType.RESOLVE_PROJECT, new HashSet<GradleTaskId>(myTasksInProgress));
return result;
}
@NotNull
@@ -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.
* <p/>
* Check service wrapper contracts for more details.
* <p/>
* 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<GradleTaskType, Set<GradleTaskId>> getTasksInProgress() throws RemoteException {
return myDelegate.getTasksInProgress();
}
}
@@ -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}.
* <p/>
* 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<GradleTaskType, Set<GradleTaskId>> getTasksInProgress() throws RemoteException {
return myResolver.getTasksInProgress();
}
}
@@ -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);
}
}
}
@@ -96,7 +96,7 @@ public abstract class AbstractGradleTask implements GradleTask {
}
finally {
for (GradleTaskNotificationListener listener : listeners) {
progressManager.addNotificationListener(getId(), listener);
progressManager.removeNotificationListener(listener);
}
}
}
@@ -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 <code>true</code> if a task with the given id is executed at the moment by the current service;
* <code>false</code> 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<GradleTaskType, Set<GradleTaskId>> getTasksInProgress() throws RemoteException;
}
@@ -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.
* <p/>
* 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:
* <pre>
* <ul>
* <li>The task is still in progress (e.g. great number of libraries is being downloaded);</li>
* <li>Remote side has fallen (uncaught exception; manual slave gradle process kill etc);</li>
* </ul>
* </pre>
* 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<GradleTaskId, Long> myTasksInProgress = new ConcurrentHashMap<GradleTaskId, Long>();
@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 <code>true</code> if any task of the given type is being executed at the moment;
* <code>false</code> 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<GradleTaskType, Set<GradleTaskId>> currentState = myFacadeManager.getFacade().getTasksInProgress();
myTasksInProgress.clear();
for (Set<GradleTaskId> 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)));
}
}
}
@@ -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:
* <pre>
* <ul>
* <li>target gradle project path is retrieved from the {@link GradleSettings gradle settings} associated with the given project;</li>
* <li>refresh process is run in background;</li>
* <li>any problem occurred during the refresh is reported to the {@link GradleLog#LOG};</li>
* </ul>
* </pre>
*
* @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<String> errorHolder = new Ref<String>();
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<String> errorHolder,
final boolean resolveLibraries,
final boolean modal)
{
final Ref<GradleProject> gradleProject = new Ref<GradleProject>();
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());
}
};
}
}