PT #172125155 Rework CP notifying mechanics for projects missing JARs

This commit is contained in:
BoykoAlex
2020-04-03 16:59:13 -04:00
parent 3bddccd57f
commit db9b41a193
3 changed files with 65 additions and 69 deletions

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}