diff --git a/atom-extensions/atom-spring-boot/properties.json b/atom-extensions/atom-spring-boot/properties.json index 97c16b6ea..2a4f68698 100644 --- a/atom-extensions/atom-spring-boot/properties.json +++ b/atom-extensions/atom-spring-boot/properties.json @@ -1,3 +1,3 @@ { - "jarUrl": "https://s3-us-west-1.amazonaws.com/s3-test.spring.io/sts4/fatjars/snapshots/spring-boot-language-server-0.2.1-201805111942.jar" + "jarUrl": "https://s3-us-west-1.amazonaws.com/s3-test.spring.io/sts4/fatjars/snapshots/spring-boot-language-server-0.4.0-201807190033.jar" } diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.boot.ls/META-INF/MANIFEST.MF index 750db8342..e0dc5a925 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/META-INF/MANIFEST.MF +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: Spring Boot Language Server Bundle-Vendor: Pivotal, Inc. Bundle-SymbolicName: org.springframework.tooling.boot.ls;singleton:=true -Bundle-Version: 0.3.2.qualifier +Bundle-Version: 0.4.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.9.0", org.eclipse.core.runtime;bundle-version="3.12.0", diff --git a/eclipse-language-servers/org.springframework.tooling.bosh.ls/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.bosh.ls/META-INF/MANIFEST.MF index 8342bf7df..85fe36bb5 100644 --- a/eclipse-language-servers/org.springframework.tooling.bosh.ls/META-INF/MANIFEST.MF +++ b/eclipse-language-servers/org.springframework.tooling.bosh.ls/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: BOSH Manifest Language Server Bundle-Vendor: Pivotal, Inc. Bundle-SymbolicName: org.springframework.tooling.bosh.ls;singleton:=true -Bundle-Version: 0.3.2.qualifier +Bundle-Version: 0.4.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.9.0", org.eclipse.core.runtime;bundle-version="3.12.0", diff --git a/eclipse-language-servers/org.springframework.tooling.bosh.ls/src/org/springframework/tooling/bosh/ls/Constants.java b/eclipse-language-servers/org.springframework.tooling.bosh.ls/src/org/springframework/tooling/bosh/ls/Constants.java index eb24fc5a1..5486b8c90 100644 --- a/eclipse-language-servers/org.springframework.tooling.bosh.ls/src/org/springframework/tooling/bosh/ls/Constants.java +++ b/eclipse-language-servers/org.springframework.tooling.bosh.ls/src/org/springframework/tooling/bosh/ls/Constants.java @@ -16,6 +16,6 @@ package org.springframework.tooling.bosh.ls; public class Constants { public static final String PLUGIN_ID = "org.springframework.tooling.bosh.ls"; - public static final String LANGUAGE_SERVER_VERSION = "0.3.2-SNAPSHOT"; + public static final String LANGUAGE_SERVER_VERSION = "0.4.0-SNAPSHOT"; } diff --git a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/META-INF/MANIFEST.MF index e71c0d208..0d024d5b8 100644 --- a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/META-INF/MANIFEST.MF +++ b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: Cloud Foundry Manifest Language Server Bundle-Vendor: Pivotal, Inc. Bundle-SymbolicName: org.springframework.tooling.cloudfoundry.manifest.ls;singleton:=true -Bundle-Version: 0.3.2.qualifier +Bundle-Version: 0.4.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.8.0", org.eclipse.core.runtime;bundle-version="3.12.0", diff --git a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/Constants.java b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/Constants.java index 8ff6afa62..f9c56a9a6 100644 --- a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/Constants.java +++ b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/Constants.java @@ -16,6 +16,6 @@ package org.springframework.tooling.cloudfoundry.manifest.ls; public class Constants { public static final String PLUGIN_ID = "org.springframework.tooling.cloudfoundry.manifest.ls"; - public static final String LANGUAGE_SERVER_VERSION = "0.3.2-SNAPSHOT.jar"; + public static final String LANGUAGE_SERVER_VERSION = "0.4.0-SNAPSHOT.jar"; } diff --git a/eclipse-language-servers/org.springframework.tooling.concourse.ls/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.concourse.ls/META-INF/MANIFEST.MF index 17fcc0f95..ec7aa2ac3 100644 --- a/eclipse-language-servers/org.springframework.tooling.concourse.ls/META-INF/MANIFEST.MF +++ b/eclipse-language-servers/org.springframework.tooling.concourse.ls/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: Concourse Pipeline Language Server Bundle-Vendor: Pivotal, Inc. Bundle-SymbolicName: org.springframework.tooling.concourse.ls;singleton:=true -Bundle-Version: 0.3.2.qualifier +Bundle-Version: 0.4.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.9.0", org.eclipse.core.runtime;bundle-version="3.12.0", diff --git a/eclipse-language-servers/org.springframework.tooling.concourse.ls/src/org/springframework/tooling/concourse/ls/Constants.java b/eclipse-language-servers/org.springframework.tooling.concourse.ls/src/org/springframework/tooling/concourse/ls/Constants.java index d8863c7a7..ecc4e9784 100644 --- a/eclipse-language-servers/org.springframework.tooling.concourse.ls/src/org/springframework/tooling/concourse/ls/Constants.java +++ b/eclipse-language-servers/org.springframework.tooling.concourse.ls/src/org/springframework/tooling/concourse/ls/Constants.java @@ -16,6 +16,6 @@ package org.springframework.tooling.concourse.ls; public class Constants { public static final String PLUGIN_ID = "org.springframework.tooling.concourse.ls"; - public static final String LANGUAGE_SERVER_VERSION = "0.3.2-SNAPSHOT"; + public static final String LANGUAGE_SERVER_VERSION = "0.4.0-SNAPSHOT"; } diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LocalSpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LocalSpringBootApp.java index a83f640a0..bd80cf64e 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LocalSpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LocalSpringBootApp.java @@ -81,7 +81,9 @@ public class LocalSpringBootApp extends AbstractSpringBootApp { @Override public String getProcessName() { - return vmd.displayName(); + String rawName = vmd.displayName(); + int firstSpace = rawName.indexOf(' '); + return firstSpace < 0 ? rawName : rawName.substring(0, firstSpace); } @Override diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/JandexClasspath.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/JandexClasspath.java index f75377567..67bbb4b63 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/JandexClasspath.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/JandexClasspath.java @@ -16,8 +16,12 @@ import java.net.URL; import java.nio.file.Files; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Optional; +import java.util.Queue; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; @@ -174,4 +178,40 @@ public final class JandexClasspath implements ClasspathIndex { .collect(CollectorUtil.toImmutableList()); } + private static void updateQueue(Queue queue, Set exclusion, IType type) { + for (String t : type.getSuperInterfaceNames()) { + if (!exclusion.contains(t)) { + queue.add(t); + exclusion.add(t); + } + } + String superClass = type.getSuperclassName(); + if (superClass != null && !exclusion.contains(superClass)) { + queue.add(superClass); + exclusion.add(superClass); + } + } + + @Override + public Flux allSuperTypesOf(IType type) { + Queue queue = new LinkedList<>(); + HashSet visited = new HashSet<>(); + updateQueue(queue, visited, type); + return Flux.generate(() -> queue, (state, sink) -> { + IType nextType = null; + while (nextType == null && state.peek() != null) { + String typeName = state.poll(); + nextType = findType(typeName); + if (nextType != null) { + sink.next(nextType); + updateQueue(state, visited, nextType); + } + } + if (state.peek() == null) { + sink.complete(); + } + return state; + }); + } + } diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java index aa4bf012f..c66b2d82c 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016-2017 Pivotal, Inc. + * Copyright (c) 2016, 2018 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 @@ -163,4 +163,15 @@ class TypeImpl implements IType { return info.toString(); } + @Override + public String getSuperclassName() { + DotName name = info.superName(); + return name == null ? null : name.toString(); + } + + @Override + public String[] getSuperInterfaceNames() { + return info.interfaceNames().stream().map(DotName::toString).toArray(String[]::new); + } + } diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/ClasspathIndex.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/ClasspathIndex.java index 38b5b46a6..6a098726c 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/ClasspathIndex.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/ClasspathIndex.java @@ -27,6 +27,7 @@ public interface ClasspathIndex extends Disposable { Flux> fuzzySearchTypes(String searchTerm, Predicate typeFilter); Flux> fuzzySearchPackages(String searchTerm); Flux allSubtypesOf(IType type); + Flux allSuperTypesOf(IType type); Optional findClasspathResourceContainer(String fqName); //Maybe the stuff below is another interface? Something that provides operations diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaProject.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaProject.java index 85d6468e3..205702ce5 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaProject.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaProject.java @@ -41,6 +41,10 @@ public interface IJavaProject { return getIndex().allSubtypesOf(targetType); } + default Flux allSuperTypesOf(IType targetType) { + return getIndex().allSuperTypesOf(targetType); + } + default Flux> fuzzySearchTypes(String searchTerm, Predicate typeFilter) { return getIndex().fuzzySearchTypes(searchTerm, typeFilter); } diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java index d5e1e22cf..8322eebac 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java @@ -54,7 +54,7 @@ public interface IType extends IMember { IField getField(String name); /** - * Returns the fields declared by this type in the order in which they appear + * Returns the fields declared by this type in the order in which they appear * in the source or class file. For binary types, this includes synthetic fields. * * @return the fields declared by this type @@ -71,7 +71,7 @@ public interface IType extends IMember { * The type signatures may be either unresolved (for source types) * or resolved (for binary types), and either basic (for basic types) * or rich (for parameterized types). See {@link Signature} for details. - * Note that the parameter type signatures for binary methods are expected + * Note that the parameter type signatures for binary methods are expected * to be dot-based. *

* @@ -86,13 +86,17 @@ public interface IType extends IMember { * For binary types, this may include the special <clinit> method * and synthetic methods. *

- * The results are listed in the order in which they appear in the source or class file. + * The results are listed in the order in which they appear in the source or class file. *

* * @return the methods and constructors declared by this type */ Stream getMethods(); + String getSuperclassName(); + + String[] getSuperInterfaceNames(); + // /** // * Resolves the given type name within the context of this type (depending on the type hierarchy // * and its imports). diff --git a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java index 665872e3f..c17b2207f 100644 --- a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java +++ b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java @@ -20,9 +20,12 @@ import static org.springframework.ide.vscode.languageserver.testharness.Classpat import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -164,4 +167,24 @@ public class JavaIndexTest { assertTrue(file.get().exists()); assertEquals(getOutputFolder(project).toString(), file.get().toString()); } + + @Test + public void testFindAllSuperTypes() throws Exception { + MavenJavaProject project = mavenProjectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + IType type = project.findType("java.util.ArrayList"); + assertNotNull(type); + Set actual = project.allSuperTypesOf(type).map(t -> t.getFullyQualifiedName()).collect(Collectors.toSet()).block(); + Set expected = new HashSet<>(Arrays.asList( + "java.util.List", + "java.util.RandomAccess", + "java.lang.Cloneable", + "java.io.Serializable", + "java.util.AbstractList", + "java.util.Collection", + "java.lang.Object", + "java.util.AbstractCollection", + "java.lang.Iterable" + )); + assertEquals(expected, actual); + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java index 418bce5b6..aedb86d9e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java @@ -10,13 +10,18 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.autowired; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; @@ -34,6 +39,7 @@ import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -41,11 +47,15 @@ import com.google.common.collect.ImmutableList; /** * @author Martin Lippert + * @author Alex Boyko */ public class AutowiredHoverProvider implements HoverProvider { final static Logger log = LoggerFactory.getLogger(AutowiredHoverProvider.class); + private static final int MAX_INLINE_BEANS_STRING_LENGTH = 50; + private static final String INLINE_BEANS_STRING_SEPARATOR = " "; + private BootJavaLanguageServerComponents server; public AutowiredHoverProvider(BootJavaLanguageServerComponents server) { @@ -94,30 +104,61 @@ public class AutowiredHoverProvider implements HoverProvider { LiveBean definedBean = getDefinedBean(annotation); if (definedBean != null) { - hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n"); - - boolean hasInterestingApp = false; - boolean hasAutowiring = false; + boolean hasContent = false; for (SpringBootApp app : runningApps) { LiveBeansModel beans = app.getBeans(); List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList()); if (!relevantBeans.isEmpty()) { - if (!hasInterestingApp) { - hasInterestingApp = true; - } else { - hover.append("\n\n"); - } - hover.append(LiveHoverUtils.niceAppName(app) + ":"); + List allDependencyBeans = relevantBeans.stream() + .flatMap(b -> Arrays.stream(b.getDependencies())) + .distinct() + .flatMap(d -> beans.getBeansOfName(d).stream()) + .collect(Collectors.toList()); - for (LiveBean bean : relevantBeans) { - hover.append("\n\n"); - hasAutowiring |= addAutomaticallyWired(hover, annotation, beans, bean, project); + if (!allDependencyBeans.isEmpty()) { + + // parent is marker node, grandparent is some field, method, variable declaration node. + ASTNode declarationNode = node.getParent().getParent(); + List autowiredBeans = findAutowiredBeans(project, declarationNode, allDependencyBeans); + if (autowiredBeans.isEmpty()) { + // Show all relevant dependency beans + autowiredBeans = allDependencyBeans; + } + + if (!autowiredBeans.isEmpty()) { + if (!hasContent) { + hasContent = true; + } else { + hover.append(" \n \n"); + } + hover.append("**Autowired → "); + if (LiveHoverUtils.doBeansFitInline(autowiredBeans, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR)) { + hover.append(autowiredBeans.stream().map(b -> LiveHoverUtils.showBeanInline(server, project, b)).collect(Collectors.joining(INLINE_BEANS_STRING_SEPARATOR))); + hover.append("**\n"); + } else { + hover.append(autowiredBeans.size()); + hover.append(" beans**\n"); + } +// if (autowiredBeans.size() == 1) { +// hover.append(LiveHoverUtils.showBeanIdAndTypeInline(server, project, autowiredBeans.get(0))); +// } else { +// hover.append(autowiredBeans.size()); +// hover.append(" beans**\n"); +// } + hover.append(autowiredBeans.stream() + .map(b -> "- " + LiveHoverUtils.showBeanWithResource(server, b, " ", project)) + .collect(Collectors.joining("\n")) + ); + hover.append("\n \n"); + hover.append(LiveHoverUtils.niceAppName(app)); + } } } + } - if (hasInterestingApp && hasAutowiring) { + if (hasContent) { return new Hover(ImmutableList.of(Either.forLeft(hover.toString()))); } } @@ -125,6 +166,53 @@ public class AutowiredHoverProvider implements HoverProvider { return null; } + @SuppressWarnings("unchecked") + private List findAutowiredBeans(IJavaProject project, ASTNode declarationNode, Collection beans) { + if (declarationNode instanceof MethodDeclaration) { + MethodDeclaration methodDeclaration = (MethodDeclaration)declarationNode; + return ((List)methodDeclaration.parameters()).stream() + .filter(p -> p instanceof SingleVariableDeclaration) + .map(p -> (SingleVariableDeclaration)p) + .flatMap(p -> findAutowiredBeans(project, p, beans).stream()) + .collect(Collectors.toList()); + } else if (declarationNode instanceof FieldDeclaration) { + FieldDeclaration fieldDeclaration = (FieldDeclaration)declarationNode; + return matchBeans(project, beans, fieldDeclaration.getType().resolveBinding()); + } else if (declarationNode instanceof SingleVariableDeclaration) { + SingleVariableDeclaration singleVariableDeclaration = (SingleVariableDeclaration)declarationNode; + return matchBeans(project, beans, singleVariableDeclaration.getType().resolveBinding()); + } + return Collections.emptyList(); + } + + private List matchBeans(IJavaProject project, Collection beans, ITypeBinding type) { + List relevant = Collections.emptyList(); + if (type != null) { + String fqName = type.getQualifiedName(); + if (fqName != null) { + relevant = matchBeans(project, beans, fqName); + if (relevant.isEmpty()) { + IType indexType = project.findType(fqName); + if (indexType != null) { + relevant = project.allSubtypesOf(indexType) + .map(subType -> matchBeans(project, beans, subType.getFullyQualifiedName())) + .filter(relevantBeans -> !relevantBeans.isEmpty()) + .blockFirst(); + } + } + } + } + return relevant; + } + + private List matchBeans(IJavaProject project, Collection beans, String fqName) { + if (fqName != null) { + return beans.stream().filter(b -> fqName.equals(b.getType(true))).collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + private LiveBean getDefinedBean(Annotation autowiredAnnotation) { TypeDeclaration declaringType = ASTUtils.findDeclaringType(autowiredAnnotation); if (declaringType != null) { @@ -150,29 +238,6 @@ public class AutowiredHoverProvider implements HoverProvider { return null; } - private boolean addAutomaticallyWired(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) { - boolean result = false; - String[] dependencies = bean.getDependencies(); - - if (dependencies != null && dependencies.length > 0) { - result = true; - hover.append(LiveHoverUtils.showBean(bean) + " got autowired with:\n\n"); - - boolean firstDependency = true; - for (String injectedBean : dependencies) { - if (!firstDependency) { - hover.append("\n"); - } - List dependencyBeans = beans.getBeansOfName(injectedBean); - for (LiveBean dependencyBean : dependencyBeans) { - hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependencyBean, " ", project)); - } - firstDependency = false; - } - } - return result; - } - @Override public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java index 0e3cc892c..4851bb21c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover; +import java.util.Collection; import java.util.Optional; import java.util.stream.Stream; @@ -21,7 +22,6 @@ import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.StringUtil; @@ -43,7 +43,9 @@ public class LiveHoverUtils { String type = bean.getType(true); StringBuilder buf = new StringBuilder("Bean: "); + buf.append('`'); buf.append(bean.getId()); + buf.append('`'); SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server); if (type != null) { // Try creating a URL link to open source for the type @@ -65,6 +67,65 @@ public class LiveHoverUtils { return buf.toString(); } + public static String showBeanInline(BootJavaLanguageServerComponents server, IJavaProject project, LiveBean bean) { + String id = bean.getId(); + String type = bean.getType(true); + StringBuilder sb = new StringBuilder(); + sb.append('`'); + sb.append(id); + sb.append('`'); + String displayId = sb.toString(); + SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server); + if (type != null) { + Optional url = sourceLinks.sourceLinkUrlForFQName(project, type); + if (url.isPresent()) { + return Renderables.link(displayId, url.get()).toMarkdown(); + } + } + return displayId; + } + + public static boolean doBeansFitInline(Collection beans, int maxLength, String delimiter) { + int length = 0; + for (LiveBean bean : beans) { + if (length != 0) { + length += delimiter.length(); + } + length += bean.getId().length(); + if (length > maxLength) { + return false; + } + } + return true; + } + + public static String showBeanIdAndTypeInline(BootJavaLanguageServerComponents server, IJavaProject project, LiveBean bean) { + String id = bean.getId(); + String type = bean.getType(true); + SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server); + String displayType = type; + if (type != null) { + int lastDotIdx = type.lastIndexOf('.'); + if (lastDotIdx >= 0 && lastDotIdx < type.length() - 1) { + displayType = "`" + type.substring(lastDotIdx + 1) + "`"; + } + Optional url = sourceLinks.sourceLinkUrlForFQName(project, type); + if (url.isPresent()) { + displayType = Renderables.link(displayType, url.get()).toMarkdown(); + } + } + StringBuilder sb = new StringBuilder(); + sb.append('`'); + sb.append(id); + sb.append('`'); + if (displayType != null) { + sb.append(' '); + sb.append(displayType); + } + return sb.toString(); + } + + public static String showResource(SourceLinks sourceLinks, String resource, IJavaProject project) { return new SpringResource(sourceLinks, resource, project).toMarkdown(); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java index cf8454cd4..01e9370c5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java @@ -156,14 +156,7 @@ public class RequestMappingHoverProvider implements HoverProvider { List renderableUrls = Arrays.stream(mappingMethod.getT1().getSplitPath()).flatMap(path -> { String url = UrlUtil.createUrl(host, port, path); - StringBuilder builder = new StringBuilder(); - builder.append("["); - builder.append(url); - builder.append("]"); - builder.append("("); - builder.append(url); - builder.append(")"); - return Stream.of(Renderables.text(builder.toString()), Renderables.lineBreak()); + return Stream.of(Renderables.link(url, url), Renderables.lineBreak()); }) .collect(Collectors.toList()); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java index 7ce68543f..1deba0edf 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java @@ -33,6 +33,34 @@ import org.springframework.ide.vscode.project.harness.ProjectsHarness.ProjectCus */ public class AutowiredHoverProviderTest { + private static final String FOO_IMPL_CONTENTS = "package com.example;\n" + + "\n" + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "import org.springframework.scheduling.TaskScheduler;\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component(\"defaultFoo\")\n" + + "public class FooImplementation implements Foo {\n" + + " \n" + + " private TaskScheduler scheduler;\n" + + " \n" + + " @Autowired Foo self;\n" + + " \n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " scheduler.scheduleWithFixedDelay(() -> {\n" + + " System.out.println(\"Doo Done done!\");\n" + + " }, 1000);\n" + + " System.out.println(\"Foo do do do do!\");\n" + + " }\n" + + "\n" + + " @Autowired\n" + + " public void setScheduler(TaskScheduler scheduler) {\n" + + " this.scheduler = scheduler;\n" + + " }\n" + + "\n" + + "}"; + private static final ProjectCustomizer FOO_INTERFACE = (CustomizableProjectContent p) -> { p.createType("com.examle.Foo", "package com.example;\n" + @@ -55,6 +83,8 @@ public class AutowiredHoverProviderTest { "public class DependencyB {\n" + "}\n" ); + + p.createType("com.example.FooImplementation", FOO_IMPL_CONTENTS); }; private BootJavaLanguageServerHarness harness; @@ -73,7 +103,7 @@ public class AutowiredHoverProviderTest { MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app", FOO_INTERFACE); assertTrue(jp.findType("com.example.Foo").exists()); - harness.useProject(projects.mavenProject("empty-boot-15-web-app")); + harness.useProject(jp); harness.intialize(null); } @@ -116,15 +146,12 @@ public class AutowiredHoverProviderTest { editor.assertHighlights("@Component", "@Inject"); editor.assertTrimmedHover("@Inject", - "**Injection report for Bean [id: autowiredClass, type: `com.example.AutowiredClass`]**\n" + - "\n" + - "Process [PID=111, name=`the-app`]:\n" + - "\n" + - "Bean [id: autowiredClass, type: `com.example.AutowiredClass`] got autowired with:\n" + - "\n" + - "- Bean: dependencyA \n" + + "**Autowired → `dependencyA`**\n" + + "- Bean: `dependencyA` \n" + " Type: `com.example.DependencyA` \n" + - " Resource: `" + Paths.get("com/example/DependencyA.class") + "`" + " Resource: `" + Paths.get("com/example/DependencyA.class") + "`\n" + + " \n" + + "Process [PID=111, name=`the-app`]" ); } @@ -174,18 +201,16 @@ public class AutowiredHoverProviderTest { editor.assertHighlights("@Component", "@Autowired"); editor.assertTrimmedHover("@Autowired", - "**Injection report for Bean [id: autowiredClass, type: `com.example.AutowiredClass`]**\n" + - "\n" + - "Process [PID=111, name=`the-app`]:\n" + - "\n" + - "Bean [id: autowiredClass, type: `com.example.AutowiredClass`] got autowired with:\n" + - "\n" + - "- Bean: dependencyA \n" + + "**Autowired → `dependencyA` `dependencyB`**\n" + + "- Bean: `dependencyA` \n" + " Type: `com.example.DependencyA` \n" + " Resource: `" + Paths.get("com/example/DependencyA.class") + "`\n" + - "- Bean: dependencyB \n" + + "- Bean: `dependencyB` \n" + " Type: `com.example.DependencyB` \n" + - " Resource: `com/example/DependencyB.class`\n" + " Resource: `com/example/DependencyB.class`\n" + + " \n" + + "Process [PID=111, name=`the-app`]\n" + ); } @@ -302,13 +327,17 @@ public class AutowiredHoverProviderTest { .add(LiveBean.builder() .id("defaultFoo") .type("com.example.FooImplementation") - .dependencies("defaultFoo") - .dependencies("otherBean") + .dependencies("superBean", "scheduler") .build() ) .add(LiveBean.builder() - .id("otherBean") - .type("com.example.DependencyA") + .id("superBean") + .type("com.example.FooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("scheduler") + .type("org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler") .build() ) .build(); @@ -320,43 +349,16 @@ public class AutowiredHoverProviderTest { .build(); - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.beans.factory.annotation.Autowired;\n" + - "import org.springframework.scheduling.TaskScheduler;\n" + - "import org.springframework.stereotype.Component;\n" + - "\n" + - "@Component(\"defaultFoo\")\n" + - "public class FooImplementation implements Foo {\n" + - " \n" + - " private TaskScheduler scheduler;\n" + - " \n" + - " @Autowired Foo self;\n" + - " \n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " scheduler.scheduleWithFixedDelay(() -> {\n" + - " System.out.println(\"Doo Done done!\");\n" + - " }, 1000);\n" + - " System.out.println(\"Foo do do do do!\");\n" + - " }\n" + - "\n" + - " @Autowired\n" + - " public void setScheduler(TaskScheduler scheduler) {\n" + - " this.scheduler = scheduler;\n" + - " }\n" + - "\n" + - "}" - ); + Editor editor = harness.newEditor(LanguageId.JAVA, FOO_IMPL_CONTENTS); editor.assertHighlights("@Component", "@Autowired", "@Autowired"); - for (int i = 1; i <= 2; i++) { - editor.assertHoverContains("@Autowired", i, - "Bean [id: defaultFoo, type: `com.example.FooImplementation`] got autowired with:\n" + - "\n" + - "- Bean: otherBean \n" + - " Type: `com.example.DependencyA`"); - } + editor.assertHoverContains("@Autowired", 1, + "**Autowired → `superBean`**\n" + + "- Bean: `superBean` \n" + + " Type: `com.example.FooImplementation`"); + editor.assertHoverContains("@Autowired", 2, + "**Autowired → `scheduler`**\n" + + "- Bean: `scheduler` \n" + + " Type: `org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler`"); } @Test public void bug_152621242_autowired_constructor_on_a_controller() throws Exception { @@ -404,9 +406,8 @@ public class AutowiredHoverProviderTest { ); editor.assertHighlights("@Controller", "@Autowired"); editor.assertHoverContains("@Autowired", - "Bean [id: myController, type: `com.example.MyController`] got autowired with:\n" + - "\n" + - "- Bean: restTemplate \n" + + "**Autowired → `restTemplate`**\n" + + "- Bean: `restTemplate` \n" + " Type: `org.springframework.web.client.RestTemplate`" ); editor.assertHoverContains("@Controller", diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeanInjectedIntoHoverProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeanInjectedIntoHoverProviderTest.java index 480131454..a995fe702 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeanInjectedIntoHoverProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeanInjectedIntoHoverProviderTest.java @@ -215,7 +215,7 @@ public class BeanInjectedIntoHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `hello.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `hello.MyController`\n" ); } @@ -275,7 +275,7 @@ public class BeanInjectedIntoHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `hello.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `hello.MyController`\n" ); } @@ -329,7 +329,7 @@ public class BeanInjectedIntoHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `hello.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `hello.MyController` \n" + " Resource: `" + Paths.get("hello/MyController.class") + "`" ); @@ -383,7 +383,7 @@ public class BeanInjectedIntoHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `hello.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `hello.MyController` \n" + " Resource: `hello/MyController.class`" ); @@ -441,9 +441,9 @@ public class BeanInjectedIntoHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `hello.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `hello.MyController`\n" + - "- Bean: otherBean \n" + + "- Bean: `otherBean` \n" + " Type: `hello.OtherBean`\n" ); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeansByTypeHoverProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeansByTypeHoverProviderTest.java index 77020029a..cea6fc61f 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeansByTypeHoverProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeansByTypeHoverProviderTest.java @@ -140,7 +140,7 @@ public class BeansByTypeHoverProviderTest { "\n" + "Bean [id: scannedRandomClass, type: `com.example.ScannedRandomClass`] injected into:\n" + "\n" + - "- Bean: randomOtherBean \n" + + "- Bean: `randomOtherBean` \n" + " Type: `randomOtherBeanType`" ); } @@ -196,11 +196,11 @@ public class BeansByTypeHoverProviderTest { "\n" + "Bean [id: scannedFunctionClass, type: `com.example.ScannedFunctionClass`] injected into:\n" + "\n" + - "- Bean: org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration \n" + + "- Bean: `org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration` \n" + " Type: `org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration`" ); } - + @Test public void generalBeanLiveHoverAvoidOverlapWithAnnotation() throws Exception { LiveBeansModel beans = LiveBeansModel.builder() diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/ComponentInjectionsHoverProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/ComponentInjectionsHoverProviderTest.java index 60d8bd601..cef834e18 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/ComponentInjectionsHoverProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/ComponentInjectionsHoverProviderTest.java @@ -165,7 +165,7 @@ public class ComponentInjectionsHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `com.example.MyController`" ); } @@ -220,9 +220,9 @@ public class ComponentInjectionsHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `com.example.MyController`\n" + - "- Bean: otherBean \n" + + "- Bean: `otherBean` \n" + " Type: `com.example.OtherBean`" ); } @@ -279,18 +279,18 @@ public class ComponentInjectionsHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `com.example.MyController`\n" + - "- Bean: otherBean \n" + + "- Bean: `otherBean` \n" + " Type: `com.example.OtherBean`\n" + "\n" + "Process [PID=1002, name=`app-instance-2`]:\n" + "\n" + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `com.example.MyController`\n" + - "- Bean: otherBean \n" + + "- Bean: `otherBean` \n" + " Type: `com.example.OtherBean`\n" ); } @@ -350,7 +350,7 @@ public class ComponentInjectionsHoverProviderTest { "\n" + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + "\n" + - "- Bean: myController \n" + + "- Bean: `myController` \n" + " Type: `com.example.MyController`" ); } @@ -410,7 +410,7 @@ public class ComponentInjectionsHoverProviderTest { "\n" + "Bean [id: alternateFooImplementation, type: `com.example.FooImplementation`] injected into:\n" + "\n" + - "- Bean: otherBean \n" + + "- Bean: `otherBean` \n" + " Type: `com.example.OtherBean`\n" ); } @@ -526,10 +526,10 @@ public class ComponentInjectionsHoverProviderTest { "\n\n" + "Bean [id: autowiredClass, type: `com.example.AutowiredClass`] got autowired with:\n" + "\n" + - "- Bean: dependencyA \n" + + "- Bean: `dependencyA` \n" + " Type: `com.example.DependencyA` \n" + " Resource: `" + Paths.get("com/example/DependencyA.class") + "`\n" + - "- Bean: dependencyB \n" + + "- Bean: `dependencyB` \n" + " Type: `com.example.DependencyB` \n" + " Resource: `com/example/DependencyB.class`" );