PT #166597649: Send classpath when project is built if necessary

This commit is contained in:
BoykoAlex
2019-06-26 23:55:36 -04:00
parent 463fb1ba87
commit cfb353f337
4 changed files with 88 additions and 23 deletions

View File

@@ -23,7 +23,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.ClasspathIndex;
import org.springframework.ide.vscode.commons.java.IJavaModuleData;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.java.JavaUtils;
import org.springframework.ide.vscode.commons.javadoc.JdtLsJavadocProvider;
@@ -65,31 +64,23 @@ public class JdtLsIndex implements ClasspathIndex {
final private Cache<JavaTypeHierarchyParams, CompletableFuture<List<IType>>> supertypesCache = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.SECONDS).build();
final private Cache<JavaTypeHierarchyParams, CompletableFuture<List<IType>>> subtypesCache = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.SECONDS).build();
final private Listener projectListener = new Listener() {
@Override
public void deleted(IJavaProject project) {
// ignore
}
@Override
public void created(IJavaProject project) {
// ignore
}
@Override
public void changed(IJavaProject project) {
binaryTypeCache.invalidateAll();
sourceTypeCache.invalidateAll();
}
};
final private Listener projectListener;
public JdtLsIndex(STS4LanguageClient client, URI projectUri, ProjectObserver projectObserver) {
this.client = client;
this.projectUri = projectUri;
this.projectObserver = projectObserver;
this.javadocProvider = new JdtLsJavadocProvider(client, projectUri.toString());
this.projectListener = ProjectObserver.onAny(project -> {
if (Objects.equals(project.getLocationUri(), projectUri)) {
binaryTypeCache.invalidateAll();
sourceTypeCache.invalidateAll();
supertypesCache.invalidateAll();
subtypesCache.invalidateAll();
}
});
this.projectObserver.addListener(projectListener);
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* Copyright (c) 2019 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -13,6 +13,11 @@ package org.springframework.tooling.jdt.ls.commons.classpath;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -51,7 +56,8 @@ public class ClasspathListenerManager {
};
public interface ClasspathListener {
public abstract void classpathChanged(IJavaProject jp);
void classpathChanged(IJavaProject jp);
default void projectBuilt(IJavaProject jp) {};
}
private class MyListener implements IElementChangedListener {
@@ -102,12 +108,31 @@ public class ClasspathListenerManager {
private ClasspathListener listener;
private MyListener myListener;
private final Logger logger;
private IResourceChangeListener workspaceListener = (event) -> {
if (event.getSource() instanceof IProject) {
projectBuilt((IProject) event.getSource());
} else if (event.getSource() instanceof IWorkspace) {
for (IProject p : ((IWorkspace)event.getSource()).getRoot().getProjects()) {
projectBuilt(p);
}
}
};
public ClasspathListenerManager(Logger logger, ClasspathListener listener) {
this.logger = logger;
logger.log("Setting up ClasspathListenerManager");
this.listener = listener;
JavaCore.addElementChangedListener(myListener=new MyListener(), ElementChangedEvent.POST_CHANGE);
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
workspace.addResourceChangeListener(workspaceListener, IResourceChangeEvent.POST_BUILD);
}
private void projectBuilt(IProject project) {
IJavaProject jp = JavaCore.create(project);
if (jp != null) {
listener.projectBuilt(jp);
}
}
public void dispose() {
@@ -115,6 +140,7 @@ public class ClasspathListenerManager {
JavaCore.removeElementChangedListener(myListener);
myListener = null;
}
ResourcesPlugin.getWorkspace().removeResourceChangeListener(workspaceListener);
}
}

View File

@@ -65,6 +65,12 @@ public class ReusableClasspathListenerHandler {
public void classpathChanged(IJavaProject jp) {
sendNotification(jp, subscribers.keySet());
}
@Override
public void projectBuilt(IJavaProject jp) {
sendNotificationOnProjectBuilt(jp, subscribers.keySet());
}
});
subscribers.put(callbackCommandId, new SendClasspathNotificationsJob(logger, conn, callbackCommandId, isBatched));
logger.log("subsribers = " + subscribers);
@@ -106,6 +112,16 @@ public class ReusableClasspathListenerHandler {
}
}
private synchronized void sendNotificationOnProjectBuilt(IJavaProject jp, Collection<String> callbackIds) {
if (subscribers!=null) {
for (String callbackId : callbackIds) {
SendClasspathNotificationsJob sendNotificationJob = subscribers.get(callbackId);
sendNotificationJob.builtProjectQueue.add(jp);
sendNotificationJob.schedule();
}
}
}
public synchronized void unsubscribe(String callbackCommandId) {
logger.log("unsubscribing from classpath changes: " + callbackCommandId);
if (subscribers != null) {

View File

@@ -14,9 +14,11 @@ import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -26,6 +28,7 @@ import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE;
import org.springframework.tooling.jdt.ls.commons.Logger;
import com.google.common.collect.ImmutableList;
@@ -49,7 +52,10 @@ public class SendClasspathNotificationsJob extends Job {
*/
private Map<String, URI> projectLocations = new HashMap<>();
public final Queue<IJavaProject> queue = new ConcurrentLinkedQueue<>();
public final Queue<IJavaProject> builtProjectQueue = new ConcurrentLinkedQueue<>();
private final Set<IJavaProject> notReadyProjects = new HashSet<>();
public SendClasspathNotificationsJob(Logger logger, ClientCommandExecutor conn, String callbackId, boolean isBatched) {
super("Send Classpath Notifications");
this.logger = logger;
@@ -99,8 +105,19 @@ public class SendClasspathNotificationsJob extends Job {
protected IStatus run(IProgressMonitor monitor) {
synchronized (projectLocations) { //Could use some Eclipse job rule. But its really a bit of a PITA to create the right one.
try {
// Try to see if classpath needs to be sent for the projects that have been
// built since classpath JAR may not have existed (not downloaded) at the time
// of classpath changed event
for (IJavaProject jp = builtProjectQueue.poll(); jp!=null; jp = builtProjectQueue.poll()) {
if (notReadyProjects.remove(jp)) {
queue.add(jp);
}
}
for (IJavaProject jp = queue.poll(); jp!=null; jp = queue.poll()) {
logger.log("Preparing classpath changed notification " + jp.getElementName());
// Project wasn't ready before but now it's about to be processed for Classpath again.
// Remove it from the set of not readt projects
notReadyProjects.remove(jp);
URI projectLoc = getProjectLocation(jp);
if (projectLoc==null) {
logger.log("Could not send event for project because no project location: "+jp.getElementName());
@@ -117,11 +134,26 @@ public class SendClasspathNotificationsJob extends Job {
Classpath classpath = Classpath.EMPTY;
if (deleted) {
// Project has been removed no need to keep in not ready projects set
notReadyProjects.remove(jp);
// projectLocations.remove(projectName);
} else {
projectLocations.put(projectName, projectLoc);
try {
classpath = ClasspathUtil.resolve(jp, logger);
List<CPE> filteredCPEs = new ArrayList<>(classpath.getEntries().size());
for (CPE cpe : classpath.getEntries()) {
// Some classpath entries don't exist yet (to be downloaded during the build). Filter them out as these JARs won't be indexed until they exist
if (!Classpath.isBinary(cpe) || new File(cpe.getPath()).exists()) {
filteredCPEs.add(cpe);
}
}
if (filteredCPEs.size() != classpath.getEntries().size()) {
// If some entries in the classpath don't exist yet add the project to not ready projects set to process later when project is built
notReadyProjects.add(jp);
// Only send effective classpath that has all entries physically present.
classpath = new Classpath(filteredCPEs);
}
} catch (Exception e) {
logger.log(e);
}