From db9b41a193e4db336132b22f1a27fee0b861d915 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Fri, 3 Apr 2020 16:59:13 -0400 Subject: [PATCH] PT #172125155 Rework CP notifying mechanics for projects missing JARs --- .../classpath/ClasspathListenerManager.java | 92 +++++++++++++------ .../ReusableClasspathListenerHandler.java | 21 +---- .../SendClasspathNotificationsJob.java | 21 +---- 3 files changed, 65 insertions(+), 69 deletions(-) diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathListenerManager.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathListenerManager.java index 56b412b1b..583cfaf20 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathListenerManager.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathListenerManager.java @@ -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); } } \ No newline at end of file diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java index afdd40c6e..c9fee3148 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java @@ -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 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); diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/SendClasspathNotificationsJob.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/SendClasspathNotificationsJob.java index 609f47251..b1e0e5df0 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/SendClasspathNotificationsJob.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/SendClasspathNotificationsJob.java @@ -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 projectLocations = new HashMap<>(); public final Queue queue = new ConcurrentLinkedQueue<>(); - public final Queue builtProjectQueue = new ConcurrentLinkedQueue<>(); - - private final Set 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); }