diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java index b8aacec00..c20afae05 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -42,7 +42,6 @@ import com.google.common.collect.ImmutableList; */ public class AutowiredHoverProvider implements HoverProvider { - @Override public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { @@ -164,4 +163,16 @@ public class AutowiredHoverProvider implements HoverProvider { return result; } + @Override + public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, + TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + + @Override + public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, + SpringBootApp[] runningApps) { + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java index 23170d694..60823e48b 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java @@ -11,9 +11,6 @@ package org.springframework.ide.vscode.boot.java.beans; import java.util.Collection; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; @@ -28,6 +25,7 @@ import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.boot.java.utils.FunctionUtils; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Log; @@ -45,10 +43,6 @@ import reactor.util.function.Tuples; */ public class BeansSymbolProvider implements SymbolProvider { - private static final String FUNCTION_FUNCTION_TYPE = Function.class.getName(); - private static final String FUNCTION_CONSUMER_TYPE = Consumer.class.getName(); - private static final String FUNCTION_SUPPLIER_TYPE = Supplier.class.getName(); - private static final String[] NAME_ATTRIBUTES = {"value", "name"}; @Override @@ -74,7 +68,7 @@ public class BeansSymbolProvider implements SymbolProvider { @Override public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { // this checks function beans that are defined as implementations of Function interfaces - Tuple3 functionBean = getFunctionBean(typeDeclaration, doc); + Tuple3 functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc); if (functionBean != null) { try { SymbolInformation symbol = new SymbolInformation( @@ -89,54 +83,6 @@ public class BeansSymbolProvider implements SymbolProvider { return ImmutableList.of(); } - protected Tuple3 getFunctionBean(TypeDeclaration typeDeclaration, TextDocument doc) { - ITypeBinding resolvedType = typeDeclaration.resolveBinding(); - if (resolvedType != null) { - return getFunctionBean(typeDeclaration, doc, resolvedType); - } - else { - return null; - } - } - - private Tuple3 getFunctionBean(TypeDeclaration typeDeclaration, TextDocument doc, - ITypeBinding resolvedType) { - - ITypeBinding[] interfaces = resolvedType.getInterfaces(); - for (ITypeBinding resolvedInterface : interfaces) { - String simplifiedType = null; - if (resolvedInterface.isParameterizedType()) { - simplifiedType = resolvedInterface.getBinaryName(); - } - else { - simplifiedType = resolvedType.getQualifiedName(); - } - - if (FUNCTION_FUNCTION_TYPE.equals(simplifiedType) || FUNCTION_CONSUMER_TYPE.equals(simplifiedType) - || FUNCTION_SUPPLIER_TYPE.equals(simplifiedType)) { - String beanName = getBeanName(typeDeclaration); - String beanType = resolvedInterface.getName(); - DocumentRegion region = ASTUtils.nodeRegion(doc, typeDeclaration.getName()); - - return Tuples.of(beanName, beanType, region); - } - else { - Tuple3 result = getFunctionBean(typeDeclaration, doc, resolvedInterface); - if (result != null) { - return result; - } - } - } - - ITypeBinding superclass = resolvedType.getSuperclass(); - if (superclass != null) { - return getFunctionBean(typeDeclaration, doc, superclass); - } - else { - return null; - } - } - protected Collection> getBeanNames(Annotation node, TextDocument doc) { Collection beanNameNodes = getBeanNameLiterals(node); @@ -197,14 +143,6 @@ public class BeansSymbolProvider implements SymbolProvider { return null; } - protected String getBeanName(TypeDeclaration typeDeclaration) { - String beanName = typeDeclaration.getName().toString(); - if (beanName.length() > 0 && Character.isUpperCase(beanName.charAt(0))) { - beanName = Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1); - } - return beanName; - } - private boolean isFunctionBean(Annotation node) { ASTNode parent = node.getParent(); if (parent instanceof MethodDeclaration) { @@ -221,8 +159,8 @@ public class BeansSymbolProvider implements SymbolProvider { returnType = method.getReturnType2().resolveBinding().getQualifiedName(); } - return FUNCTION_FUNCTION_TYPE.equals(returnType) || FUNCTION_CONSUMER_TYPE.equals(returnType) - || FUNCTION_SUPPLIER_TYPE.equals(returnType); + return FunctionUtils.FUNCTION_FUNCTION_TYPE.equals(returnType) || FunctionUtils.FUNCTION_CONSUMER_TYPE.equals(returnType) + || FunctionUtils.FUNCTION_SUPPLIER_TYPE.equals(returnType); } return false; } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java index 66f879643..f2e598251 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -155,4 +155,16 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { return false; } + @Override + public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, + TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + + @Override + public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, + SpringBootApp[] runningApps) { + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java index 4e8740734..5a8298069 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -22,7 +22,9 @@ import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MarkerAnnotation; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentIdentifier; @@ -86,10 +88,22 @@ public class BootJavaHoverProvider implements HoverHandler { try { if (cu != null) { cu.accept(new ASTVisitor() { + + @Override + public boolean visit(TypeDeclaration node) { + try { + extractLiveHintsForType(node, document, runningBootApps, result); + } + catch (Exception e) { + e.printStackTrace(); + } + return super.visit(node); + } + @Override public boolean visit(SingleMemberAnnotation node) { try { - extractLiveHints(node, document, runningBootApps, result); + extractLiveHintsForAnnotation(node, document, runningBootApps, result); } catch (Exception e) { Log.log(e); } @@ -100,7 +114,7 @@ public class BootJavaHoverProvider implements HoverHandler { @Override public boolean visit(NormalAnnotation node) { try { - extractLiveHints(node, document, runningBootApps, result); + extractLiveHintsForAnnotation(node, document, runningBootApps, result); } catch (Exception e) { Log.log(e); } @@ -111,7 +125,7 @@ public class BootJavaHoverProvider implements HoverHandler { @Override public boolean visit(MarkerAnnotation node) { try { - extractLiveHints(node, document, runningBootApps, result); + extractLiveHintsForAnnotation(node, document, runningBootApps, result); } catch (Exception e) { Log.log(e); } @@ -127,10 +141,29 @@ public class BootJavaHoverProvider implements HoverHandler { }); } - protected void extractLiveHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps, Collection result) { + protected void extractLiveHintsForType(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps, Collection result) { + Collection providers = this.hoverProviders.getAll(); + if (!providers.isEmpty()) { + for (HoverProvider provider : providers) { + getProject(doc).ifPresent(project -> { + if (hasActuatorDependency(project)) { + Collection hints = provider.getLiveHoverHints(typeDeclaration, doc, runningApps); + if (hints!=null) { + result.addAll(hints); + } + } else { + //Do nothing... we don't want a highlight for the 'no actuator warning' + //ASTUtils.nameRange(doc, annotation).ifPresent(result::add); + } + }); + } + } + } + + protected void extractLiveHintsForAnnotation(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps, Collection result) { ITypeBinding type = annotation.resolveTypeBinding(); if (type != null) { - if (runningApps.length>0) { + if (runningApps.length > 0) { for (HoverProvider provider : this.hoverProviders.get(type)) { getProject(doc).ifPresent(project -> { if (hasActuatorDependency(project)) { @@ -154,7 +187,7 @@ public class BootJavaHoverProvider implements HoverHandler { return server.getCompilationUnitCache().withCompilationUnit(document, cu -> { ASTNode node = NodeFinder.perform(cu, offset, 0); if (node != null) { - return provideHoverForAnnotation(node, offset, document, project); + return provideHover(node, offset, document, project); } return null; }); @@ -162,32 +195,42 @@ public class BootJavaHoverProvider implements HoverHandler { return null; } - private Hover provideHoverForAnnotation(ASTNode node, int offset, TextDocument doc, IJavaProject project) { - Annotation annotation = null; + private Hover provideHover(ASTNode node, int offset, TextDocument doc, IJavaProject project) { - while (node != null && !(node instanceof Annotation)) { - node = node.getParent(); + // look for spring annotations first + ASTNode annotationNode = node; + while (annotationNode != null && !(annotationNode instanceof Annotation)) { + annotationNode = annotationNode.getParent(); + } + if (annotationNode != null) { + return provideHoverForAnnotation(node, (Annotation) annotationNode, offset, doc, project); } - if (node != null) { - annotation = (Annotation) node; - ITypeBinding type = annotation.resolveTypeBinding(); - if (type != null) { - SpringBootApp[] runningApps = getRunningSpringApps(project); - if (runningApps.length>0) { - for (HoverProvider provider : this.hoverProviders.get(type)) { - Hover hover = provider.provideHover(node, annotation, type, offset, doc, project, runningApps); - if (hover!=null) { - //TODO: compose multiple hovers somehow instead of just returning the first one? - return hover; - } + // then do additional AST node coverage + if (node instanceof SimpleName && node.getParent() instanceof TypeDeclaration) { + return provideHoverForTypeDeclaration(node, (TypeDeclaration) node.getParent(), offset, doc, project); + } + + return null; + } + + private Hover provideHoverForAnnotation(ASTNode exactNode, Annotation annotation, int offset, TextDocument doc, IJavaProject project) { + ITypeBinding type = annotation.resolveTypeBinding(); + if (type != null) { + SpringBootApp[] runningApps = getRunningSpringApps(project); + if (runningApps.length > 0) { + for (HoverProvider provider : this.hoverProviders.get(type)) { + Hover hover = provider.provideHover(exactNode, annotation, type, offset, doc, project, runningApps); + if (hover!=null) { + //TODO: compose multiple hovers somehow instead of just returning the first one? + return hover; } - //Only reaching here if we didn't get a hover. - if (!hasActuatorDependency(project)) { - DocumentRegion region = ASTUtils.nameRegion(doc, annotation); - if (region.containsOffset(offset)) { - return actuatorWarning(project); - } + } + //Only reaching here if we didn't get a hover. + if (!hasActuatorDependency(project)) { + DocumentRegion region = ASTUtils.nameRegion(doc, annotation); + if (region.containsOffset(offset)) { + return actuatorWarning(project); } } } @@ -195,6 +238,22 @@ public class BootJavaHoverProvider implements HoverHandler { return null; } + private Hover provideHoverForTypeDeclaration(ASTNode exactNode, TypeDeclaration typeDeclaration, int offset, TextDocument doc, IJavaProject project) { + SpringBootApp[] runningApps = getRunningSpringApps(project); + if (runningApps.length > 0) { + ITypeBinding type = typeDeclaration.resolveBinding(); + + for (HoverProvider provider : this.hoverProviders.getAll()) { + Hover hover = provider.provideHover(exactNode, typeDeclaration, type, offset, doc, project, runningApps); + if (hover!=null) { + //TODO: compose multiple hovers somehow instead of just returning the first one? + return hover; + } + } + } + return null; + } + private Hover actuatorWarning(IJavaProject project) { String hoverText = "**No live hover information available**.\n"+ diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java index 2e735c6b4..4dcf3598a 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -15,6 +15,7 @@ import java.util.Collection; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; @@ -27,6 +28,9 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; public interface HoverProvider { Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps); + Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps); + Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps); + Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps); } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java index 2b06e46ab..9a6c89abe 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -22,6 +22,7 @@ import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -132,4 +133,17 @@ public class ActiveProfilesProvider implements HoverProvider { return Optional.empty(); } } + + @Override + public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, + TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + + @Override + public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, + SpringBootApp[] runningApps) { + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/BeanInjectedIntoHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/BeanInjectedIntoHoverProvider.java index 7ec27d226..e4e18e923 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/BeanInjectedIntoHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/BeanInjectedIntoHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -10,13 +10,22 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover; +import java.util.Collection; import java.util.Optional; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.Hover; +import org.eclipse.lsp4j.Range; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +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.java.IJavaProject; import org.springframework.ide.vscode.commons.util.Optionals; +import org.springframework.ide.vscode.commons.util.text.TextDocument; public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider { @@ -61,4 +70,16 @@ public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProv ); } + @Override + public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, + TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + + @Override + public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, + SpringBootApp[] runningApps) { + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java index e9a3ed9d7..8c3d2c507 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -10,19 +10,36 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover; +import java.util.Collection; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MarkerAnnotation; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.Hover; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.boot.java.utils.FunctionUtils; +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.DocumentRegion; +import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +import com.google.common.collect.ImmutableList; + +import reactor.util.function.Tuple3; public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverProvider { @@ -76,6 +93,10 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP public static LiveBean getDefinedBeanForComponent(Annotation annotation) { //Move to ASTUtils? TypeDeclaration declaringType = ASTUtils.getAnnotatedType(annotation); + return getDefinedBeanForType(declaringType, annotation); + } + + private static LiveBean getDefinedBeanForType(TypeDeclaration declaringType, Annotation annotation) { if (declaringType != null) { ITypeBinding beanType = declaringType.resolveBinding(); if (beanType != null) { @@ -106,4 +127,63 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP }); } + @Override + public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, + SpringBootApp[] runningApps) { + Tuple3 functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc); + if (functionBean != null && runningApps.length > 0) { + try { + LiveBean definedBean = getDefinedBeanForType(typeDeclaration, null); + if (definedBean != null) { + if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) { + Optional nameRange = Optional.of(ASTUtils.nodeRegion(doc, typeDeclaration.getName()).asRange()); + if (nameRange.isPresent()) { + return ImmutableList.of(nameRange.get()); + } + } + } + } catch (Exception e) { + Log.log(e); + } + } + return ImmutableList.of(); + } + + @Override + public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, + TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + Tuple3 functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc); + if (functionBean != null && runningApps.length > 0) { + + LiveBean definedBean = getDefinedBeanForType(typeDeclaration, null); + if (definedBean != null) { + StringBuilder hover = new StringBuilder(); + hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n"); + + boolean hasInterestingApp = 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) + ":"); + + for (LiveBean bean : relevantBeans) { + addInjectedInto(definedBean, hover, beans, bean, project); + } + } + } + if (hasInterestingApp) { + return new Hover(ImmutableList.of(Either.forLeft(hover.toString()))); + } + } + } + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java index 1726a4cab..cf8454cd4 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -22,6 +22,7 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.MarkedString; import org.eclipse.lsp4j.Range; @@ -178,4 +179,17 @@ public class RequestMappingHoverProvider implements HoverProvider { } } + + @Override + public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, + TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + + @Override + public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, + SpringBootApp[] runningApps) { + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java index 47a0b7901..96f9172e5 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -77,26 +77,28 @@ public class ASTUtils { } public static Optional getAttribute(Annotation annotation, String name) { - try { - if (annotation.isSingleMemberAnnotation() && name.equals("value")) { - SingleMemberAnnotation sma = (SingleMemberAnnotation) annotation; - return Optional.ofNullable(sma.getValue()); - } else if (annotation.isNormalAnnotation()) { - NormalAnnotation na = (NormalAnnotation) annotation; - Object attributeObjs = na.getStructuralProperty(NormalAnnotation.VALUES_PROPERTY); - if (attributeObjs instanceof List) { - for (Object atrObj : (List)attributeObjs) { - if (atrObj instanceof MemberValuePair) { - MemberValuePair mvPair = (MemberValuePair) atrObj; - if (name.equals(mvPair.getName().getIdentifier())) { - return Optional.ofNullable(mvPair.getValue()); + if (annotation != null) { + try { + if (annotation.isSingleMemberAnnotation() && name.equals("value")) { + SingleMemberAnnotation sma = (SingleMemberAnnotation) annotation; + return Optional.ofNullable(sma.getValue()); + } else if (annotation.isNormalAnnotation()) { + NormalAnnotation na = (NormalAnnotation) annotation; + Object attributeObjs = na.getStructuralProperty(NormalAnnotation.VALUES_PROPERTY); + if (attributeObjs instanceof List) { + for (Object atrObj : (List)attributeObjs) { + if (atrObj instanceof MemberValuePair) { + MemberValuePair mvPair = (MemberValuePair) atrObj; + if (name.equals(mvPair.getName().getIdentifier())) { + return Optional.ofNullable(mvPair.getValue()); + } } } } } + } catch (Exception e) { + Log.log(e); } - } catch (Exception e) { - Log.log(e); } return Optional.empty(); } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/FunctionUtils.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/FunctionUtils.java new file mode 100644 index 000000000..a41ae61e3 --- /dev/null +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/FunctionUtils.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +import reactor.util.function.Tuple3; +import reactor.util.function.Tuples; + +public class FunctionUtils { + + public static final String FUNCTION_FUNCTION_TYPE = Function.class.getName(); + public static final String FUNCTION_CONSUMER_TYPE = Consumer.class.getName(); + public static final String FUNCTION_SUPPLIER_TYPE = Supplier.class.getName(); + + public static Tuple3 getFunctionBean(TypeDeclaration typeDeclaration, TextDocument doc) { + ITypeBinding resolvedType = typeDeclaration.resolveBinding(); + if (resolvedType != null) { + return getFunctionBean(typeDeclaration, doc, resolvedType); + } + else { + return null; + } + } + + private static Tuple3 getFunctionBean(TypeDeclaration typeDeclaration, TextDocument doc, + ITypeBinding resolvedType) { + + ITypeBinding[] interfaces = resolvedType.getInterfaces(); + for (ITypeBinding resolvedInterface : interfaces) { + String simplifiedType = null; + if (resolvedInterface.isParameterizedType()) { + simplifiedType = resolvedInterface.getBinaryName(); + } + else { + simplifiedType = resolvedType.getQualifiedName(); + } + + if (FUNCTION_FUNCTION_TYPE.equals(simplifiedType) || FUNCTION_CONSUMER_TYPE.equals(simplifiedType) + || FUNCTION_SUPPLIER_TYPE.equals(simplifiedType)) { + String beanName = getBeanName(typeDeclaration); + String beanType = resolvedInterface.getName(); + DocumentRegion region = ASTUtils.nodeRegion(doc, typeDeclaration.getName()); + + return Tuples.of(beanName, beanType, region); + } + else { + Tuple3 result = getFunctionBean(typeDeclaration, doc, resolvedInterface); + if (result != null) { + return result; + } + } + } + + ITypeBinding superclass = resolvedType.getSuperclass(); + if (superclass != null) { + return getFunctionBean(typeDeclaration, doc, superclass); + } + else { + return null; + } + } + + protected static String getBeanName(TypeDeclaration typeDeclaration) { + String beanName = typeDeclaration.getName().toString(); + if (beanName.length() > 0 && Character.isUpperCase(beanName.charAt(0))) { + beanName = Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1); + } + return beanName; + } + +} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java index b67050379..9f104c6b4 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 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 @@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -199,4 +200,16 @@ public class ValueHoverProvider implements HoverProvider { } + @Override + public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, + TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + + @Override + public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, + SpringBootApp[] runningApps) { + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/FunctionInjectionsHoverProviderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/FunctionInjectionsHoverProviderTest.java new file mode 100644 index 000000000..07704ff77 --- /dev/null +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/FunctionInjectionsHoverProviderTest.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.livehover.test; + +import java.time.Duration; + +import org.junit.Before; +import org.junit.Test; +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.maven.java.MavenJavaProject; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.languageserver.testharness.Editor; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.MockRunningAppProvider; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; + +public class FunctionInjectionsHoverProviderTest { + + private BootLanguageServerHarness harness; + private ProjectsHarness projects = ProjectsHarness.INSTANCE; + + private MockRunningAppProvider mockAppProvider; + + @Before + public void setup() throws Exception { + mockAppProvider = new MockRunningAppProvider(); + harness = BootLanguageServerHarness.builder() + .mockDefaults() + .runningAppProvider(mockAppProvider.provider) + .watchDogInterval(Duration.ofMillis(100)) + .build(); + + MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app"); + harness.useProject(jp); + harness.intialize(null); + } + + @Test + public void componentWithNoInjections() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("scannedFunctionClass") + .type("com.example.ScannedFunctionClass") + .build() + ) + .add(LiveBean.builder() + .id("org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration") + .type("org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration") + .dependencies("scannedFunctionClass") + .build() + ) + .add(LiveBean.builder() + .id("irrelevantBean") + .type("com.example.IrrelevantBean") + .dependencies("myController") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import java.util.function.Function;\n" + + "\n" + + "public class ScannedFunctionClass implements Function {\n" + + "\n" + + " @Override\n" + + " public String apply(String t) {\n" + + " return t.toUpperCase();\n" + + " }\n" + + "\n" + + "}\n" + + "" + ); + editor.assertHighlights("ScannedFunctionClass"); + editor.assertTrimmedHover("ScannedFunctionClass", + "**Injection report for Bean [id: scannedFunctionClass, type: `com.example.ScannedFunctionClass`]**\n" + + "\n" + + "Process [PID=111, name=`the-app`]:\n" + + "\n" + + "Bean [id: scannedFunctionClass, type: `com.example.ScannedFunctionClass`] injected into:\n" + + "\n" + + "- Bean: org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration \n" + + " Type: `org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration`" + ); + } + +}