diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/BootProjectDashElement.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/BootProjectDashElement.java index acf582a7e..66b1bbf26 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/BootProjectDashElement.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/BootProjectDashElement.java @@ -12,6 +12,8 @@ package org.springframework.ide.eclipse.boot.dash.model; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobGroup; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jdt.core.IJavaElement; @@ -53,6 +55,8 @@ import com.google.common.collect.ImmutableSortedSet; @SuppressWarnings("restriction") public class BootProjectDashElement extends AbstractLaunchConfigurationsDashElement implements BootDashElement, LiveDataCapableElement { + private static final JobGroup mainMethodJobGroup = new JobGroup("Find 'main' methods in spring boot projects...", 5, 5); + private static final boolean DEBUG = DebugUtil.isDevelopment(); private static void debug(String string) { @@ -67,29 +71,35 @@ public class BootProjectDashElement extends AbstractLaunchConfigurationsDashElem private ObservableSet children; private ObservableSet ports; - private LiveExpression hasMainMethod = new AsyncLiveExpression(false) { + private AsyncLiveExpression hasMainMethod = new AsyncLiveExpression( + false, + "Search main' method in project " + (getProject() != null && getProject().getName() != null ? "'" + getProject().getName() + "'": "UNKNOWQN"), + null, + job -> { + job.setPriority(Job.DECORATE); + job.setJobGroup(mainMethodJobGroup); + }) { - @Override - protected Boolean compute() { - IProject p = getProject(); - Log.info("Has main method computation for project '" + p.getName() + "'"); - if (p != null && p.exists()) { - IJavaProject jp = JavaCore.create(p); - if (jp != null) { - IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(new IJavaElement[]{jp}, IJavaSearchScope.SOURCES); - MainMethodSearchEngine engine = new MainMethodSearchEngine(); - IType[] types = null; - try { - types = engine.searchMainMethods(new NullProgressMonitor(), searchScope, false); - return types != null && types.length > 0; - } - catch (Exception e) { - Log.log(e); + @Override + protected Boolean compute() { + IProject p = getProject(); + if (p != null && p.exists()) { + IJavaProject jp = JavaCore.create(p); + if (jp != null) { + IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(new IJavaElement[]{jp}, IJavaSearchScope.SOURCES); + MainMethodSearchEngine engine = new MainMethodSearchEngine(); + IType[] types = null; + try { + types = engine.searchMainMethods(new NullProgressMonitor(), searchScope, false); + return types != null && types.length > 0; + } + catch (Exception e) { + Log.log(e); + } + } } + return false; } - } - return false; - } }; @@ -145,7 +155,7 @@ public class BootProjectDashElement extends AbstractLaunchConfigurationsDashElem private ObservableSet getLivePortsExp() { if (ports==null) { ports = createSortedLiveSummary((BootDashElement element) -> { - int port = element.getLivePort(); + int port = CollectionUtils.getAnyOr(getLivePorts(), -1);; if (port>0) { return port; } diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/LocalBootDashModel.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/LocalBootDashModel.java index e62abb887..3e3ec8f53 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/LocalBootDashModel.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/LocalBootDashModel.java @@ -12,13 +12,22 @@ package org.springframework.ide.eclipse.boot.dash.model; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IJavaProject; import org.osgi.framework.Bundle; @@ -141,6 +150,7 @@ public class LocalBootDashModel extends AbstractBootDashModel implements Deletio if (event.getBundle() == bundle && event.getType() == BundleEvent.STARTED) { try { updateElementsFromWorkspace(); + applications.getValue().stream().forEach(e -> e.refreshHasMainMethod()); } catch (Throwable t) { Log.log(t); } finally { @@ -173,6 +183,31 @@ public class LocalBootDashModel extends AbstractBootDashModel implements Deletio this.devtoolsPortRefresher = new DevtoolsPortRefresher(this, projectElementFactory); + final IResourceChangeListener workspaceBuildListener = new IResourceChangeListener() { + + @Override + public void resourceChanged(IResourceChangeEvent event) { + if (event.getSource() != null) { + BuildDetectorVisitor visitor = new BuildDetectorVisitor(); + try { + event.getDelta().accept(visitor); + for (IProject p : visitor.getProjects()) { + Optional found = applications.getValue().stream() + .filter(e -> p.equals(e.getProject())).findFirst(); + found.ifPresent(bde -> bde.refreshHasMainMethod()); + } + } catch (CoreException e) { + Log.log(e); + } + } + + } + }; + + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + workspace.addResourceChangeListener(workspaceBuildListener, IResourceChangeEvent.POST_BUILD); + addDisposableChild(() -> workspace.removeResourceChangeListener(workspaceBuildListener)); + addDisposableChild(getViewModel().getToggleFilters().getSelectedFilters().onChange((e, v) -> { if (e.getValue().contains(ToggleFiltersModel.FILTER_CHOICE_HIDE_NOT_RUNNABLE_APPS)) { for (BootProjectDashElement a : applications.getValue()) { @@ -183,6 +218,26 @@ public class LocalBootDashModel extends AbstractBootDashModel implements Deletio } } + private static class BuildDetectorVisitor implements IResourceDeltaVisitor { + + private Set projects = new HashSet<>(); + + @Override + public boolean visit(IResourceDelta delta) throws CoreException { + IResource resource = delta.getResource(); + if (resource instanceof IProject) { + projects.add((IProject) resource); + return false; + } + return true; + } + + public Set getProjects() { + return projects; + } + + } + /** * When no longer needed the model should be disposed, otherwise it will continue * listening for changes to the workspace in order to keep itself in synch. @@ -238,7 +293,6 @@ public class LocalBootDashModel extends AbstractBootDashModel implements Deletio .map(projectElementFactory::createOrGet) .filter(Objects::nonNull) .collect(Collectors.toSet()); - newElements.stream().forEach(e -> e.refreshHasMainMethod()); apps.replaceAll(newElements); projectElementFactory.disposeAllExcept(newElements); } @@ -259,6 +313,7 @@ public class LocalBootDashModel extends AbstractBootDashModel implements Deletio */ public void refresh(UserInteractions ui) { updateElementsFromWorkspace(); + applications.getValue().stream().forEach(e -> e.refreshHasMainMethod()); localServices.refresh(); } diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.livexp/src/org/springsource/ide/eclipse/commons/livexp/core/AsyncLiveExpression.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.livexp/src/org/springsource/ide/eclipse/commons/livexp/core/AsyncLiveExpression.java index 9a66b88ff..10d013a5c 100644 --- a/eclipse-language-servers/org.springsource.ide.eclipse.commons.livexp/src/org/springsource/ide/eclipse/commons/livexp/core/AsyncLiveExpression.java +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.livexp/src/org/springsource/ide/eclipse/commons/livexp/core/AsyncLiveExpression.java @@ -10,13 +10,15 @@ *******************************************************************************/ package org.springsource.ide.eclipse.commons.livexp.core; +import static org.springsource.ide.eclipse.commons.livexp.core.AsyncLiveExpression.AsyncMode.ASYNC; + +import java.util.function.Consumer; + import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; -import static org.springsource.ide.eclipse.commons.livexp.core.AsyncLiveExpression.AsyncMode.*; - /** * Like a LiveExpression but has an option to ensures that its refresh * method is always called in a background job. @@ -88,14 +90,21 @@ public abstract class AsyncLiveExpression extends LiveExpression { * synchronous) LiveExpression. */ public AsyncLiveExpression(T initialValue, String refreshJobName, String eventsJobName) { + this(initialValue, refreshJobName, eventsJobName, null); + } + + public AsyncLiveExpression(T initialValue, String refreshJobName, String eventsJobName, Consumer refreshJobCustomizer) { super(initialValue); if (refreshJobName!=null) { refreshJob = new Job(refreshJobName) { protected IStatus run(IProgressMonitor monitor) { syncRefresh(); return Status.OK_STATUS; - }; + } }; + if (refreshJobCustomizer != null) { + refreshJobCustomizer.accept(refreshJob); + } } if (eventsJobName!=null) { eventsJob = new Job(eventsJobName) { diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.livexp/src/org/springsource/ide/eclipse/commons/livexp/core/LiveSetVariable.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.livexp/src/org/springsource/ide/eclipse/commons/livexp/core/LiveSetVariable.java index 1744f81ea..16be8bd14 100644 --- a/eclipse-language-servers/org.springsource.ide.eclipse.commons.livexp/src/org/springsource/ide/eclipse/commons/livexp/core/LiveSetVariable.java +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.livexp/src/org/springsource/ide/eclipse/commons/livexp/core/LiveSetVariable.java @@ -15,8 +15,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import org.springsource.ide.eclipse.commons.livexp.core.ObservableSet; - import com.google.common.collect.ImmutableSet; /** @@ -81,6 +79,7 @@ public class LiveSetVariable extends ObservableSet { synchronized (this) { if (!dirty) return; //bail out fast and don't copy the collection needlessly value = compute(); + dirty = false; } //Note... we are being careful here to put the 'changed' call outside synch block. // only keep locks for short time while maniping the collection / dirty state.