diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/ClasspathData.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/ClasspathData.java index 4488fd050..1e97c5287 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/ClasspathData.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/ClasspathData.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2019 Pivotal, Inc. + * Copyright (c) 2018, 2023 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 @@ -12,12 +12,16 @@ package org.springframework.ide.vscode.commons.java; import java.util.Collection; import java.util.Collections; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.ExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableSet; public class ClasspathData implements IClasspath { @@ -31,12 +35,15 @@ public class ClasspathData implements IClasspath { private String name; private Set classpathEntries; + + private Cache> binaryLibLookupCache; public ClasspathData() {} public ClasspathData(String name, Collection classpathEntries) { this.name = name; this.classpathEntries = ImmutableSet.copyOf(classpathEntries); + this.binaryLibLookupCache = CacheBuilder.newBuilder().build(); } @@ -71,6 +78,16 @@ public class ClasspathData implements IClasspath { this.classpathEntries = classpathEntries; } + @Override + public Optional findBinaryLibrary(String prefix) { + try { + return binaryLibLookupCache.get(prefix, () -> IClasspath.super.findBinaryLibrary(prefix)); + } catch (ExecutionException e) { + log.error("", e); + return Optional.empty(); + } + } + public static ClasspathData getEmptyClasspathData() { return EMPTY_CLASSPATH_DATA; } diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IClasspath.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IClasspath.java index 096efcb1a..3951d8731 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IClasspath.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IClasspath.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2019 Pivotal, Inc. + * Copyright (c) 2016, 2023 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 @@ -10,10 +10,13 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.java; +import java.io.File; import java.util.Collection; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.commons.protocol.java.Classpath; import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE; /** @@ -36,4 +39,23 @@ public interface IClasspath { * @throws Exception */ Collection getClasspathEntries() throws Exception; + + /** + * Finds a classpath entry among JAR libraries that start with a prefix. Prefix must typically contain the full lib name such that the match is only one. + * @param prefix the library prefix + * @return the classpath entry + */ + default Optional findBinaryLibrary(String prefix) { + try { + for (CPE cpe : getClasspathEntries()) { + if (Classpath.isBinary(cpe) && !cpe.isSystem() && !cpe.isTest() && new File(cpe.getPath()).getName().startsWith(prefix)) { + return Optional.of(cpe); + } + } + } catch (Exception e) { + log.error("", e); + } + return Optional.empty(); } + +} diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java index 8425f8f6e..f133403be 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java @@ -11,11 +11,10 @@ package org.springframework.ide.vscode.commons.java; import java.io.File; -import java.util.List; +import java.util.Optional; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,15 +33,15 @@ public class SpringProjectUtil { private static final Pattern GENERATION_VERSION = Pattern.compile(GENERATION_VERSION_STR); public static boolean isSpringProject(IJavaProject jp) { - return hasSpecificLibraryOnClasspath(jp, "spring-core", true); + return jp.getClasspath().findBinaryLibrary("spring-core").isPresent(); } public static boolean isBootProject(IJavaProject jp) { - return hasSpecificLibraryOnClasspath(jp, SPRING_BOOT, true); + return jp.getClasspath().findBinaryLibrary(SPRING_BOOT).isPresent(); } public static boolean hasBootActuators(IJavaProject jp) { - return hasSpecificLibraryOnClasspath(jp, "spring-boot-actuator-", true); + return jp.getClasspath().findBinaryLibrary("spring-boot-actuator-").isPresent(); } /** @@ -80,94 +79,44 @@ public class SpringProjectUtil { throw new IllegalArgumentException("Invalid semver. Unable to parse major and minor version from: " + name); } - public static List getLibrariesOnClasspath(IJavaProject jp, String libraryNamePrefix) { - try { - IClasspath cp = jp.getClasspath(); - if (cp!=null) { - boolean onlyLibs = true; - List libs = IClasspathUtil.getBinaryRoots(cp, (cpe) -> !cpe.isSystem()).stream().filter(cpe -> isEntry(cpe, libraryNamePrefix, onlyLibs)).collect(Collectors.toList()); - return libs; - } - } catch (Exception e) { - log.error("Failed to get list of libraries for project '" + jp.getElementName() + "' that start with prefix: " + libraryNamePrefix, e); - } - return null; - } - - public static boolean hasSpecificLibraryOnClasspath(IJavaProject jp, String libraryNamePrefix, boolean onlyLibs) { - try { - IClasspath cp = jp.getClasspath(); - if (cp!=null) { - return IClasspathUtil.getBinaryRoots(cp, (cpe) -> !cpe.isSystem()).stream().anyMatch(cpe -> isEntry(cpe, libraryNamePrefix, onlyLibs)); - } - } catch (Exception e) { - log.error("Failed to determine whether '" + jp.getElementName() + "' is Spring Boot project", e); - } - return false; - } - - private static boolean isEntry(File cpe, String libNamePrefix, boolean onlyLibs) { - String name = cpe.getName(); - return name.startsWith(libNamePrefix) && (!onlyLibs || name.endsWith(".jar")); - } - public static Version getDependencyVersion(IJavaProject jp, String dependency) { - try { - for (CPE cpe : jp.getClasspath().getClasspathEntries()) { - if (Classpath.isBinary(cpe) && !cpe.isSystem() && new File(cpe.getPath()).getName().startsWith(dependency)) { - return cpe.getVersion(); - } - } - } catch (Exception e) { - log.error("", e); - } - return null; + return jp.getClasspath().findBinaryLibrary(dependency).map(cpe -> cpe.getVersion()).orElse(null); } public static boolean hasDependencyStartingWith(IJavaProject jp, String dependency, Predicate filter) { - try { - for (CPE cpe : jp.getClasspath().getClasspathEntries()) { - if (filter == null || filter.test(cpe)) { - if (Classpath.ENTRY_KIND_BINARY.equals(cpe.getKind())) { - String name = new File(cpe.getPath()).getName(); - if (name.endsWith(".jar") && name.startsWith(dependency)) { - return true; - } - } else if (Classpath.ENTRY_KIND_SOURCE.equals(cpe.getKind()) && !cpe.isOwn()) { - if (cpe.getExtra() != null && cpe.getExtra().containsKey("project")) { - if (new File(cpe.getExtra().get("project")).getName().startsWith(dependency)) { - return true; - } - } else { - if (new File(cpe.getPath()).getName().startsWith(dependency)) { - return true; + IClasspath classpath = jp.getClasspath(); + return classpath.findBinaryLibrary(dependency).or(() -> { + try { + for (CPE cpe : classpath.getClasspathEntries()) { + if (filter == null || filter.test(cpe)) { + if (Classpath.ENTRY_KIND_SOURCE.equals(cpe.getKind()) && !cpe.isOwn()) { + if (cpe.getExtra() != null && cpe.getExtra().containsKey("project")) { + if (new File(cpe.getExtra().get("project")).getName().startsWith(dependency)) { + return Optional.of(cpe); + } + } else { + if (new File(cpe.getPath()).getName().startsWith(dependency)) { + return Optional.of(cpe); + } } } } } + } catch (Exception e) { + log.error("", e); } - } catch (Exception e) { - log.error("", e); - } - return false; + return Optional.empty(); + }).isEmpty(); } + - public static Version getSpringBootVersion(IJavaProject jp) { - try { - for (CPE cpe : jp.getClasspath().getClasspathEntries()) { - if (Classpath.isBinary(cpe) && !cpe.isSystem() && new File(cpe.getPath()).getName().startsWith(SPRING_BOOT)) { - return cpe.getVersion(); - } - } - } catch (Exception e) { - log.error("", e); - } - return null; + public static Version getSpringBootVersion(IJavaProject jp) { + return getDependencyVersion(jp, SPRING_BOOT); } public static Predicate springBootVersionGreaterOrEqual(int major, int minor, int patch) { return project -> { - Version version = getDependencyVersion(project, SPRING_BOOT); + Version version = project.getClasspath().findBinaryLibrary(SPRING_BOOT).map(cpe -> cpe.getVersion()).orElse(null); if (version == null) { return false; } diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/Classpath.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/Classpath.java index a6c3d845d..0caf218ae 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/Classpath.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/Classpath.java @@ -217,7 +217,7 @@ public class Classpath { public Version getVersion() { if (version == null) { - if (getKind() == ENTRY_KIND_BINARY) { + if (isBinary(this) && !isSystem) { version = getDependencyVersion(new File(getPath()).getName()); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java index 6476bc427..b3f821dba 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java @@ -40,7 +40,6 @@ import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.java.SpringProjectUtil; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; @@ -50,8 +49,7 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler { @Override public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) throws RequiredCompleteAstException { - final boolean considerIdField = SpringProjectUtil.hasSpecificLibraryOnClasspath(project, "spring-data-mongodb-", - true); + final boolean considerIdField = project.getClasspath().findBinaryLibrary("spring-data-mongodb-").isPresent(); cu.accept(new ASTVisitor() { @Override diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectVersionDiagnosticProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectVersionDiagnosticProvider.java index 7013e5cbe..9842c545a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectVersionDiagnosticProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectVersionDiagnosticProvider.java @@ -66,8 +66,7 @@ public class ProjectVersionDiagnosticProvider { } protected File getSpringBootDependency(IJavaProject project) { - List libs = SpringProjectUtil.getLibrariesOnClasspath(project, "spring-boot"); - return libs != null && libs.size() > 0 ? libs.get(0) : null; + return project.getClasspath().findBinaryLibrary(SpringProjectUtil.SPRING_BOOT).map(cpe -> new File(cpe.getPath())).orElse(null); } public static class DiagnosticResult {