PT #172125155 Rework CP notifying mechanics for projects missing JARs
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2019, 2020 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,11 +13,8 @@ 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.resources.IFile;
|
||||
import org.eclipse.core.resources.IResourceDelta;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
@@ -27,6 +24,7 @@ import org.eclipse.jdt.core.IElementChangedListener;
|
||||
import org.eclipse.jdt.core.IJavaElement;
|
||||
import org.eclipse.jdt.core.IJavaElementDelta;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.IPackageFragmentRoot;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.springframework.tooling.jdt.ls.commons.Logger;
|
||||
|
||||
@@ -75,8 +73,14 @@ public class ClasspathListenerManager {
|
||||
visitChildren(delta);
|
||||
break;
|
||||
case IJavaElement.JAVA_PROJECT:
|
||||
if (isCreatedOrDeleted(delta) || isClasspathChanged(delta.getFlags())) {
|
||||
listener.classpathChanged((IJavaProject)el);
|
||||
IJavaProject jp = (IJavaProject)el;
|
||||
if (isCreatedOrDeleted(delta)
|
||||
|| isClasspathChanged(delta.getFlags())
|
||||
// Classpath unchanged but maven/gradle repo cache has JAR's removed or downloaded
|
||||
// See individual method comments for more details
|
||||
|| isClasspathManifestFileChanged(jp, delta)
|
||||
|| areClasspathJarsChanged(delta)) {
|
||||
listener.classpathChanged(jp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -84,6 +88,56 @@ public class ClasspathListenerManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any classpath JARs have been added/removed/content changed
|
||||
* For the case when classpath stays the same while some classpath JARs are missing from maven/gradle repo cache.
|
||||
* This handles Gradle case completely and partially handles Maven case
|
||||
* @param delta
|
||||
* @return
|
||||
*/
|
||||
private boolean areClasspathJarsChanged(IJavaElementDelta delta) {
|
||||
for (IJavaElementDelta childDelta : delta.getAffectedChildren()) {
|
||||
if (childDelta.getElement() instanceof IPackageFragmentRoot) {
|
||||
IPackageFragmentRoot pkgRoot = (IPackageFragmentRoot) childDelta.getElement();
|
||||
if (pkgRoot.isArchive()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When Maven project update is completed .classpath file content changed is one
|
||||
* of the resource delta's expected.
|
||||
*
|
||||
* If maven cache JARs hav ebeen removed and then maven project update performed
|
||||
* the following events sent: 1. JAR files removed 2. Once classpath is ready
|
||||
* and downloaded .classpath file content changed comes in
|
||||
*
|
||||
* Next update of the same project will result in the following 1. JAR files
|
||||
* added 2. Immidiately after .classpath file content changed
|
||||
*
|
||||
* Looks like M2E does a "refresh" before updating which is good, but no refresh
|
||||
* after which is bad and hence JAR files added event come next Maven Update and
|
||||
* we are forced to watch for .classpath content changed.
|
||||
*
|
||||
* @param jp
|
||||
* @param delta
|
||||
* @return
|
||||
*/
|
||||
private boolean isClasspathManifestFileChanged(IJavaProject jp, IJavaElementDelta delta) {
|
||||
if (delta.getResourceDeltas() != null && (delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) != 0) {
|
||||
IFile classpathFile = jp.getProject().getFile(IJavaProject.CLASSPATH_FILE_NAME);
|
||||
for (IResourceDelta resourceDelta : delta.getResourceDeltas()) {
|
||||
if (classpathFile.equals(resourceDelta.getResource())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isCreatedOrDeleted(IJavaElementDelta delta) {
|
||||
int kind = delta.getKind();
|
||||
return kind == IJavaElementDelta.ADDED || kind==IJavaElementDelta.REMOVED;
|
||||
@@ -109,38 +163,18 @@ public class ClasspathListenerManager {
|
||||
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.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() {
|
||||
if (myListener!=null) {
|
||||
JavaCore.removeElementChangedListener(myListener);
|
||||
myListener = null;
|
||||
}
|
||||
ResourcesPlugin.getWorkspace().removeResourceChangeListener(workspaceListener);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2020 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
|
||||
@@ -99,25 +99,15 @@ public class ReusableClasspathListenerHandler {
|
||||
|
||||
public void subscribe(String callbackCommandId, boolean isBatched) {
|
||||
// keep out of synchronized block to avoid workspace locks
|
||||
logger.log("Sorting projects...");
|
||||
IProject[] sortedProjects = getSortedProjects();
|
||||
logger.log("Sorting projects... DONE");
|
||||
|
||||
synchronized(this) {
|
||||
logger.log("inside synchronized Subscriptions");
|
||||
if (!subscribers.containsKey(callbackCommandId)) {
|
||||
logger.log("subscribing to classpath changes: " + callbackCommandId +" isBatched = "+isBatched);
|
||||
classpathListener = new ClasspathListenerManager(logger, new ClasspathListener() {
|
||||
@Override
|
||||
public void classpathChanged(IJavaProject jp) {
|
||||
sendNotification(jp, subscribers.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void projectBuilt(IJavaProject jp) {
|
||||
sendNotificationOnProjectBuilt(jp, subscribers.keySet());
|
||||
}
|
||||
|
||||
});
|
||||
final SendClasspathNotificationsJob job = new SendClasspathNotificationsJob(logger, conn, callbackCommandId, isBatched);
|
||||
subscribers.put(callbackCommandId, job);
|
||||
@@ -132,7 +122,6 @@ public class ReusableClasspathListenerHandler {
|
||||
}
|
||||
|
||||
});
|
||||
logger.log("subsribers = " + subscribers);
|
||||
sendInitialEvents(callbackCommandId, sortedProjects);
|
||||
}
|
||||
}
|
||||
@@ -177,14 +166,6 @@ public class ReusableClasspathListenerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void sendNotificationOnProjectBuilt(IJavaProject jp, Collection<String> callbackIds) {
|
||||
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);
|
||||
subscribers.remove(callbackCommandId);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2020 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
|
||||
@@ -14,11 +14,9 @@ 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 java.util.stream.Collectors;
|
||||
|
||||
@@ -55,9 +53,6 @@ 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");
|
||||
@@ -109,19 +104,9 @@ public class SendClasspathNotificationsJob extends Job {
|
||||
notificationsSentForProjects = null;
|
||||
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());
|
||||
@@ -138,8 +123,6 @@ 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);
|
||||
@@ -153,8 +136,6 @@ public class SendClasspathNotificationsJob extends Job {
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user