diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AbstractSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AbstractSymbolProvider.java index ec86c2d94..bc5e37b0a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AbstractSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AbstractSymbolProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Pivotal, Inc. + * Copyright (c) 2019, 2022 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,13 +12,13 @@ package org.springframework.ide.vscode.boot.java.handlers; import java.util.Collection; -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.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.ClassDeclaration; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.java.tree.JavaType.FullyQualified; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS; -import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; +import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert @@ -26,7 +26,7 @@ import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; public class AbstractSymbolProvider implements SymbolProvider { @Override - public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(Annotation node, FullyQualified typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { if (SCAN_PASS.ONE.equals(context.getPass())) { addSymbolsPass1(node, typeBinding, metaAnnotations, context, doc); } @@ -36,7 +36,7 @@ public class AbstractSymbolProvider implements SymbolProvider { } @Override - public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(ClassDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { if (SCAN_PASS.ONE.equals(context.getPass())) { addSymbolsPass1(typeDeclaration, context, doc); } @@ -60,19 +60,19 @@ public class AbstractSymbolProvider implements SymbolProvider { // implementations can decide whether to implement just pass1 or if they need 2 phases, they would have to implement both methods (pass1 + pass2) // - protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + protected void addSymbolsPass1(Annotation node, FullyQualified typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { } - protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + protected void addSymbolsPass1(ClassDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { } protected void addSymbolsPass1(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { } - protected void addSymbolsPass2(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + protected void addSymbolsPass2(Annotation node, FullyQualified typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { } - protected void addSymbolsPass2(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + protected void addSymbolsPass2(ClassDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { } protected void addSymbolsPass2(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CodeLensProvider.java index 8d3bb82f6..a1f4f1fb1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CodeLensProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CodeLensProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2021 Pivotal, Inc. + * Copyright (c) 2017, 2022 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,9 +12,9 @@ package org.springframework.ide.vscode.boot.java.handlers; import java.util.List; -import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.openrewrite.java.tree.J.CompilationUnit; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java index a69db8b9d..856216acf 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2019 Pivotal, Inc. + * Copyright (c) 2017, 2022 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,10 +12,10 @@ package org.springframework.ide.vscode.boot.java.handlers; import java.util.Collection; -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.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.ClassDeclaration; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.java.tree.JavaType.FullyQualified; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -25,8 +25,8 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; */ public interface SymbolProvider { - void addSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc); - void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc); + void addSymbols(Annotation node, FullyQualified typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc); + void addSymbols(ClassDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc); void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc); } 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 10bc46e2c..bc098d8e7 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2022 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 @@ -18,16 +18,17 @@ import java.util.Set; 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.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.ClassDeclaration; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.java.tree.J.VariableDeclarations; +import org.openrewrite.java.tree.JavaType.FullyQualified; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; @@ -35,6 +36,7 @@ import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils; import org.springframework.ide.vscode.boot.java.livehover.v2.LiveRequestMapping; import org.springframework.ide.vscode.boot.java.livehover.v2.RequestMappingMetrics; import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveData; +import org.springframework.ide.vscode.boot.java.utils.ORAstUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; @@ -58,8 +60,8 @@ public class RequestMappingHoverProvider implements HoverProvider { private static final int CODE_LENS_LIMIT = 3; @Override - public Hover provideHover(ASTNode node, Annotation annotation, - ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringProcessLiveData[] processLiveData) { + public Hover provideHover(J node, Annotation annotation, + int offset, TextDocument doc, IJavaProject project, SpringProcessLiveData[] processLiveData) { return provideHover(annotation, doc, processLiveData); } @@ -69,7 +71,8 @@ public class RequestMappingHoverProvider implements HoverProvider { if (processLiveData.length > 0) { List> val = getRequestMappingMethodFromRunningApp(annotation, processLiveData); if (!val.isEmpty()) { - Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); + org.openrewrite.marker.Range r = ORAstUtils.getRange(annotation); + Range hoverRange = doc.toRange(r.getStart().getOffset(), r.length()); return assembleCodeLenses(hoverRange, val); } } @@ -175,7 +178,8 @@ public class RequestMappingHoverProvider implements HoverProvider { if (!val.isEmpty()) { Hover hover = createHoverWithContent(val); - Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); + org.openrewrite.marker.Range r = ORAstUtils.getRange(annotation); + Range hoverRange = doc.toRange(r.getStart().getOffset(), r.length()); hover.setRange(hoverRange); return hover; } else { @@ -220,21 +224,26 @@ public class RequestMappingHoverProvider implements HoverProvider { rqClassName = rqClassName.replace('$', '.'); - ASTNode parent = annotation.getParent(); + J parent = ORAstUtils.getParent(annotation); if (parent instanceof MethodDeclaration) { MethodDeclaration methodDec = (MethodDeclaration) parent; - IMethodBinding binding = methodDec.resolveBinding(); - if (binding != null) { - return binding.getDeclaringClass().getQualifiedName().equals(rqClassName) - && binding.getName().equals(rm.getMethodName()) - && Arrays.equals(Arrays.stream(binding.getParameterTypes()) - .map(t -> t.getTypeDeclaration().getQualifiedName()) - .toArray(String[]::new), - rm.getMethodParameters()); + ClassDeclaration declaringClass = ORAstUtils.findNode(methodDec, ClassDeclaration.class); + if (declaringClass != null) { + FullyQualified type = declaringClass.getType(); + return type != null && type.getFullyQualifiedName().equals(rqClassName) + && methodDec.getSimpleName().equals(rm.getMethodName()) + && Arrays.equals(rm.getMethodParameters(), + methodDec.getParameters().stream() + .filter(VariableDeclarations.class::isInstance) + .map(VariableDeclarations.class::cast) + .map(v -> v.getTypeAsFullyQualified()) // what about primitive types, arrays etc??? + .map(fq -> fq == null ? "" : fq.getFullyQualifiedName()) + .toArray(String[]::new) + ); } - // } else if (parent instanceof TypeDeclaration) { - // TypeDeclaration typeDec = (TypeDeclaration) parent; - // return typeDec.resolveBinding().getQualifiedName().equals(rqClassName); + } else if (parent instanceof ClassDeclaration) { + ClassDeclaration typeDec = (ClassDeclaration) parent; + return typeDec.getType() == null ? false : rqClassName.equals(typeDec.getType().getFullyQualifiedName()); } } return false; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java index 0325318d9..ac6239a99 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java @@ -17,20 +17,20 @@ import java.util.List; import java.util.Objects; 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.Expression; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MemberValuePair; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.NormalAnnotation; -import org.eclipse.jdt.core.dom.SingleMemberAnnotation; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.Assignment; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.java.tree.JavaType.FullyQualified; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; +import org.springframework.ide.vscode.boot.java.utils.ORAstUtils; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -40,11 +40,12 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; public class RequestMappingSymbolProvider extends AbstractSymbolProvider { @Override - protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + protected void addSymbolsPass1(Annotation node, FullyQualified annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { - if (node.getParent() instanceof MethodDeclaration) { + if (ORAstUtils.getParent(node) instanceof MethodDeclaration) { try { - Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); + Range r = ORAstUtils.getRange(node); + Location location = new Location(doc.getUri(), doc.toRange(r.getStart().getOffset(), r.length())); String[] path = getPath(node, context); String[] parentPath = getParentPath(node, context); String[] methods = getMethod(node, context); @@ -69,9 +70,40 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider { } } } + + private String[] getRequestMethodFromMultiArgs(Annotation annotation, SpringIndexerJavaContext context) { + FullyQualified type = TypeUtils.asFullyQualified(annotation.getType()); + if (type != null && Annotations.SPRING_REQUEST_MAPPING.equals(type.getFullyQualifiedName())) { + for (Expression arg : annotation.getArguments()) { + if (arg instanceof Assignment) { + Assignment assign = (Assignment) arg; + if ("method".equals(assign.getVariable().printTrimmed())) { + return ORAstUtils.getExpressionValueAsArray(assign.getAssignment(), context::addDependency); + } + } + } + } + return null; + } private String[] getMethod(Annotation node, SpringIndexerJavaContext context) { String[] methods = null; + + + List args = node.getArguments(); + + // TODO: OR AST Annotation parameter + if (args == null || (args.size() == 1 + && (!(args.get(0) instanceof Assignment)) || "value".equals(((Assignment)args.get(0)).getVariable().printTrimmed()) ) + ) { + methods = getRequestMethod(node); + } else if () { + for (Expression arg : args) { + if (arg instanceof Assignment && "method".equals(((Assignment) arg).getVariable().printTrimmed())) { + + } + } + } if (node.isNormalAnnotation()) { NormalAnnotation normNode = (NormalAnnotation) node; @@ -156,10 +188,10 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider { return null; } - private String[] getRequestMethod(SingleMemberAnnotation annotation) { - ITypeBinding type = annotation.resolveTypeBinding(); + private String[] getRequestMethod(Annotation annotation) { + FullyQualified type = TypeUtils.asFullyQualified(annotation.getType()); if (type != null) { - switch (type.getQualifiedName()) { + switch (type.getFullyQualifiedName()) { case Annotations.SPRING_GET_MAPPING: return new String[] { "GET" }; case Annotations.SPRING_POST_MAPPING: @@ -170,6 +202,8 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider { return new String[] { "PUT" }; case Annotations.SPRING_PATCH_MAPPING: return new String[] { "PATCH" }; + case Annotations.SPRING_REQUEST_MAPPING: + return new String[] { "GET" }; } } return null; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java index 20fa8c0a9..444fce57e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java @@ -12,16 +12,18 @@ package org.springframework.ide.vscode.boot.java.requestmapping; import java.util.List; -import org.eclipse.jdt.core.dom.ASTVisitor; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J.ClassDeclaration; +import org.openrewrite.java.tree.J.CompilationUnit; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider; import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; +import org.springframework.ide.vscode.boot.java.utils.ORAstUtils; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -38,25 +40,23 @@ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { @Override public void provideCodeLenses(CancelChecker cancelToken, TextDocument document, CompilationUnit cu, List resultAccumulator) { - cu.accept(new ASTVisitor() { - @Override - public boolean visit(MethodDeclaration node) { - provideCodeLens(cancelToken, node, document, resultAccumulator); - return super.visit(node); - } - }); + new JavaIsoVisitor>() { + public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, List cl) { + provideCodeLens(cancelToken, method, document, cl); + return method; + }; + }.visitNonNull(cu, resultAccumulator); } - protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node, TextDocument document, List resultAccumulator) { + protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration method, TextDocument document, List resultAccumulator) { cancelToken.checkCanceled(); - IMethodBinding methodBinding = node.resolveBinding(); + ClassDeclaration declaringType = ORAstUtils.findDeclaringType(method); + + if (method != null && ORAstUtils.findDeclaringType(method) != null) { - if (methodBinding != null && methodBinding.getDeclaringClass() != null && methodBinding.getMethodDeclaration() != null - && methodBinding.getDeclaringClass().getBinaryName() != null && methodBinding.getMethodDeclaration().toString() != null) { - - final String handlerClass = methodBinding.getDeclaringClass().getBinaryName().trim(); - final String handlerMethod = methodBinding.getMethodDeclaration().toString().trim(); + final String handlerClass = declaringType.getType().getFullyQualifiedName(); + final String handlerMethod = method.getMethodType().toString(); // TODO: OR AST likely a problem cancelToken.checkCanceled(); @@ -75,7 +75,8 @@ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { WebfluxHandlerInformation handlerInfo = (WebfluxHandlerInformation) object; CodeLens codeLens = new CodeLens(); - codeLens.setRange(document.toRange(node.getName().getStartPosition(), node.getName().getLength())); + Range r = ORAstUtils.getRange(method.getName()); + codeLens.setRange(document.toRange(r.getStart().getOffset(), r.length())); String httpMethod = WebfluxUtils.getStringRep(handlerInfo.getHttpMethods(), string -> string); String codeLensCommand = httpMethod != null ? httpMethod + " " : ""; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java index 8f07f0c88..2e792c8a7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java @@ -10,24 +10,33 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils; +import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.function.Consumer; 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.ArrayInitializer; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.StringLiteral; -import org.openrewrite.java.tree.J.Modifier; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.Range; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.commons.util.CollectorUtil; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -38,6 +47,24 @@ public class ASTUtils { private static final Logger log = LoggerFactory.getLogger(ASTUtils.class); + public static DocumentRegion nameRegion(TextDocument doc, Annotation annotation) { + int start = annotation.getTypeName().getStartPosition(); + int end = start + annotation.getTypeName().getLength(); + if (doc.getSafeChar(start - 1) == '@') { + start--; + } + return new DocumentRegion(doc, start, end); + } + + public static Optional nameRange(TextDocument doc, Annotation annotation) { + try { + return Optional.of(nameRegion(doc, annotation).asRange()); + } catch (Exception e) { + log.error("", e); + return Optional.empty(); + } + } + public static DocumentRegion stringRegion(TextDocument doc, StringLiteral node) { DocumentRegion nodeRegion = nodeRegion(doc, node); if (nodeRegion.startsWith("\"")) { @@ -50,6 +77,109 @@ public class ASTUtils { } + public static DocumentRegion nodeRegion(TextDocument doc, ASTNode node) { + int start = node.getStartPosition(); + int end = start + node.getLength(); + return new DocumentRegion(doc, start, end); + } + + public static Optional getAttribute(Annotation annotation, String name) { + 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.error("", e); + } + } + return Optional.empty(); + } + + /** + * For case where a expression can be either a String or a array of Strings and + * we are interested in the first element of the array. (I.e. typical case + * when annotation attribute is of type String[] (because Java allows using a single + * value as a convenient syntax for writing an array of length 1 in that case. + */ + public static Optional getFirstString(Expression exp) { + if (exp instanceof StringLiteral) { + return Optional.ofNullable(getLiteralValue((StringLiteral) exp)); + } else if (exp instanceof ArrayInitializer) { + ArrayInitializer array = (ArrayInitializer) exp; + Object objs = array.getStructuralProperty(ArrayInitializer.EXPRESSIONS_PROPERTY); + if (objs instanceof List) { + List list = (List) objs; + if (!list.isEmpty()) { + Object firstObj = list.get(0); + if (firstObj instanceof Expression) { + return getFirstString((Expression) firstObj); + } + } + } + } + return Optional.empty(); + } + + public static TypeDeclaration findDeclaringType(ASTNode node) { + while (node != null && !(node instanceof TypeDeclaration)) { + node = node.getParent(); + } + + return node != null ? (TypeDeclaration) node : null; + } + + public static boolean hasExactlyOneConstructor(TypeDeclaration typeDecl) { + boolean oneFound = false; + MethodDeclaration[] methods = typeDecl.getMethods(); + for (MethodDeclaration methodDeclaration : methods) { + if (methodDeclaration.isConstructor()) { + if (oneFound) { + return false; + } else { + oneFound = true; + } + } + } + return oneFound; + } + + public static MethodDeclaration getAnnotatedMethod(Annotation annotation) { + ASTNode parent = annotation.getParent(); + if (parent instanceof MethodDeclaration) { + return (MethodDeclaration)parent; + } + return null; + } + + public static TypeDeclaration getAnnotatedType(Annotation annotation) { + ASTNode parent = annotation.getParent(); + if (parent instanceof TypeDeclaration) { + return (TypeDeclaration)parent; + } + return null; + } + + public static String getLiteralValue(StringLiteral node) { + synchronized (node.getAST()) { + return node.getLiteralValue(); + } + } + public static String getExpressionValueAsString(Expression exp, Consumer dependencies) { if (exp instanceof StringLiteral) { return getLiteralValue((StringLiteral) exp); @@ -114,6 +244,20 @@ public class ASTUtils { } + public static Collection getAnnotations(TypeDeclaration declaringType) { + Object modifiersObj = declaringType.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY); + if (modifiersObj instanceof List) { + ImmutableList.Builder annotations = ImmutableList.builder(); + for (Object node : (List)modifiersObj) { + if (node instanceof Annotation) { + annotations.add((Annotation) node); + } + } + return annotations.build(); + } + return ImmutableList.of(); + } + public static String getAnnotationType(Annotation annotation) { ITypeBinding binding = annotation.resolveTypeBinding(); @@ -122,5 +266,41 @@ public class ASTUtils { } return null; } - + + public static Optional beanId(List modifiers) { + return modifiers.stream() + .filter(m -> m instanceof SingleMemberAnnotation) + .map(m -> (SingleMemberAnnotation) m) + .filter(m -> { + ITypeBinding typeBinding = m.resolveTypeBinding(); + if (typeBinding != null) { + return Annotations.QUALIFIER.equals(typeBinding.getQualifiedName()); + } + return false; + }) + .findFirst() + .map(a -> a.getValue()) + .filter(e -> e != null) + .map(e -> e.resolveConstantExpressionValue()) + .filter(o -> o instanceof String) + .map(o -> (String) o); + } + + public static Annotation getBeanAnnotation(MethodDeclaration method) { + List modifiers = method.modifiers(); + for (Object modifier : modifiers) { + if (modifier instanceof Annotation) { + Annotation annotation = (Annotation) modifier; + ITypeBinding typeBinding = annotation.resolveTypeBinding(); + if (typeBinding != null) { + String fqName = typeBinding.getQualifiedName(); + if (Annotations.BEAN.equals(fqName)) { + return annotation; + } + } + } + } + return null; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/DefaultSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/DefaultSymbolProvider.java index c238aa1f7..e54553bd8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/DefaultSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/DefaultSymbolProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2022 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,11 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils; -import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -22,8 +23,9 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; public class DefaultSymbolProvider { public static SymbolInformation provideDefaultSymbol(Annotation node, TextDocument doc) throws Exception { + Range r = ORAstUtils.getRange(node); SymbolInformation symbol = new SymbolInformation(node.toString(), SymbolKind.Interface, - new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()))); + new Location(doc.getUri(), doc.toRange(r.getStart().getOffset(), r.length()))); return symbol; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORAstUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORAstUtils.java index 2da9509e1..dcad5138f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORAstUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORAstUtils.java @@ -1,17 +1,34 @@ package org.springframework.ide.vscode.boot.java.utils; -import java.util.ArrayList; +import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.stream.Collectors; -import org.openrewrite.Cursor; -import org.openrewrite.SourceFile; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.Recipe; +import org.openrewrite.Result; import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.UpdateSourcePositions; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.Annotation; @@ -19,6 +36,8 @@ import org.openrewrite.java.tree.J.Assignment; import org.openrewrite.java.tree.J.ClassDeclaration; import org.openrewrite.java.tree.J.CompilationUnit; import org.openrewrite.java.tree.J.EnumValueSet; +import org.openrewrite.java.tree.J.FieldAccess; +import org.openrewrite.java.tree.J.Identifier; import org.openrewrite.java.tree.J.Literal; import org.openrewrite.java.tree.J.MethodDeclaration; import org.openrewrite.java.tree.J.NewArray; @@ -37,41 +56,110 @@ public class ORAstUtils { private static final Logger log = LoggerFactory.getLogger(ORAstUtils.class); - private static class AncestersMarker implements Marker { + private static class ParentMarker implements Marker { private UUID uuid; - private List ancesters = List.of(); + private J parent; - public AncestersMarker(List ancesters) { + public ParentMarker(J parent) { this.uuid = Tree.randomId(); - this.ancesters = ancesters; + this.parent = parent; } - + @Override public UUID getId() { return uuid; } - @SuppressWarnings("unchecked") - public T getFirstAnsector(Class clazz) { - if (ancesters != null) { - for (J node : ancesters) { - if (clazz.isInstance(node)) { - return (T) node; - } - } + public J getParent() { + return parent; + } + + public J getGrandParent() { + if (parent != null) { + return parent.getMarkers().findFirst(ParentMarker.class).map(m -> m.getParent()).orElse(null); } return null; } - public J getParent() { - if (ancesters != null && !ancesters.isEmpty()) { - return ancesters.get(0); + public T getFirstAnsector(Class clazz) { + if (clazz.isInstance(parent)) { + return clazz.cast(parent); + } else if (parent != null) { + return parent.getMarkers().findFirst(ParentMarker.class).map(m -> m.getFirstAnsector(clazz)).orElse(null); } return null; } } +// private static class AncestersMarker implements Marker { +// +// private UUID uuid; +// private List ancesters = List.of(); +// +// public AncestersMarker(List ancesters) { +// this.uuid = Tree.randomId(); +// this.ancesters = ancesters; +// } +// +// @Override +// public UUID getId() { +// return uuid; +// } +// +// @SuppressWarnings("unchecked") +// public T getFirstAnsector(Class clazz) { +// if (ancesters != null) { +// for (J node : ancesters) { +// if (clazz.isInstance(node)) { +// return (T) node; +// } +// } +// } +// return null; +// } +// +// public J getParent() { +// if (ancesters != null && !ancesters.isEmpty()) { +// return ancesters.get(0); +// } +// return null; +// } +// +// public J getGrandParent() { +// if (ancesters != null && ancesters.size() > 1) { +// return ancesters.get(1); +// } +// return null; +// } +// } + + private static class MarkParentRecipe extends Recipe { + + @Override + public String getDisplayName() { + return "Create parent AST node references via markers"; + } + + @Override + protected TreeVisitor getVisitor() { + return new JavaIsoVisitor<>() { + @Override + public @Nullable J visit(@Nullable Tree tree, ExecutionContext p) { + if (tree instanceof J) { + J j = (J) tree; + J parent = getCursor().getParent() == null ? null : getCursor().getParent().getValue(); + J newJ = j.withMarkers(j.getMarkers().addIfAbsent(new ParentMarker(parent))); + super.visit(newJ, p); + return newJ; + } + return (J) tree; + } + }; + } + + } + public static J findAstNodeAt(CompilationUnit cu, int offset) { AtomicReference f = new AtomicReference<>(); new JavaIsoVisitor>() { @@ -87,16 +175,18 @@ public class ORAstUtils { && offset <= range.getEnd().getOffset()) { super.visit(tree, found); if (found.get() == null) { - List ancesters = new ArrayList<>(); - for (Cursor c = getCursor(); c != null && !(c.getValue() instanceof SourceFile); c = c.getParent()) { - Object o = c.getValue(); - if (o instanceof J) { - ancesters.add((J) o); - } - } - J n = node.withMarkers(node.getMarkers().addIfAbsent(new AncestersMarker(ancesters))); - found.set(n); - return n; +// List ancesters = new ArrayList<>(); +// for (Cursor c = getCursor(); c != null && !(c.getValue() instanceof SourceFile); c = c.getParent()) { +// Object o = c.getValue(); +// if (o instanceof J) { +// ancesters.add((J) o); +// } +// } +// J n = node.withMarkers(node.getMarkers().addIfAbsent(new AncestersMarker(ancesters))); +// found.set(n); +// return n; + found.set(node); + return node; } } else { return (J) tree; @@ -113,13 +203,15 @@ public class ORAstUtils { if (clazz.isInstance(node)) { return (T) node; } - AncestersMarker ancestry = node.getMarkers().findFirst(AncestersMarker.class).orElseThrow(); - return ancestry.getFirstAnsector(clazz); +// AncestersMarker ancestry = node.getMarkers().findFirst(AncestersMarker.class).orElseThrow(); +// return ancestry.getFirstAnsector(clazz); + return node.getMarkers().findFirst(ParentMarker.class).map(m -> m.getFirstAnsector(clazz)).orElse(null); } public static J getParent(J node) { - AncestersMarker ancestry = node.getMarkers().findFirst(AncestersMarker.class).orElseThrow(); - return ancestry.getParent(); +// AncestersMarker ancestry = node.getMarkers().findFirst(AncestersMarker.class).orElseThrow(); +// return ancestry.getParent(); + return node.getMarkers().findFirst(ParentMarker.class).map(m -> m.getParent()).orElse(null); } public static EnumValueSet getEnumValues(ClassDeclaration classDecl) { @@ -293,5 +385,48 @@ public class ORAstUtils { return null; } + public static J getGrandParent(J j) { + return j.getMarkers().findFirst(ParentMarker.class).map(m -> m.getGrandParent()).orElse(null); + } + + public static String[] getExpressionValueAsArray(Expression exp, Consumer dependencies) { + if (exp instanceof NewArray) { + NewArray array = (NewArray) exp; + return array.getInitializer().stream().map(e -> getExpressionValueAsString(e, dependencies)) + .filter(Objects::nonNull).toArray(String[]::new); + } else { + String rm = getExpressionValueAsString(exp, dependencies); + if (rm != null) { + return new String[] { rm }; + } + } + return null; + } + + public static String getExpressionValueAsString(Expression exp, Consumer dependencies) { + // TODO: OR AST need to check if there is way to extract constant values from variables + if (exp instanceof Literal) { + return getLiteralValue((Literal) exp); + } else if (exp instanceof Identifier) { + Identifier id = (Identifier) exp; + return id.getSimpleName(); + } else if (exp instanceof FieldAccess) { + FieldAccess fa = (FieldAccess) exp; + return getExpressionValueAsString(fa.getName(), dependencies); + } else { + return null; + } + } + public static List parse(JavaParser parser, Iterable sourceFiles) { + List cus = parser.parse(sourceFiles, null, new InMemoryExecutionContext()); + List results = new UpdateSourcePositions().doNext(new MarkParentRecipe()).run(cus); + return results.stream().map(r -> r.getAfter() == null ? r.getBefore() : r.getAfter()).map(CompilationUnit.class::cast).collect(Collectors.toList()); + } + + public static List parseInputs(JavaParser parser, Iterable inputs) { + List cus = parser.parseInputs(inputs, null, new InMemoryExecutionContext()); + List results = new UpdateSourcePositions().doNext(new MarkParentRecipe()).run(cus); + return results.stream().map(r -> r.getAfter() == null ? r.getBefore() : r.getAfter()).map(CompilationUnit.class::cast).collect(Collectors.toList()); + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORCompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORCompilationUnitCache.java index a9e28cb9f..f4cb4524a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORCompilationUnitCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORCompilationUnitCache.java @@ -208,12 +208,10 @@ public class ORCompilationUnitCache implements DocumentContentProvider, Disposab } }); - List parseInputs = javaParser.parseInputs(List.of(input), null, new InMemoryExecutionContext()); - Result result = new UpdateSourcePositions().run(parseInputs).get(0); - logger.info("CU Cache: created new AST for {}", uri.toString()); - - return (CompilationUnit) (result.getAfter() == null ? result.getBefore() : result.getAfter()); + + return ORAstUtils.parseInputs(javaParser, List.of(input)).get(0); + }); if (cu != null) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java index 77ff8bf87..9eeb340a8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2019 Pivotal, Inc. + * Copyright (c) 2018, 2022 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 @@ -11,13 +11,12 @@ package org.springframework.ide.vscode.boot.java.utils; import java.util.Collection; -import java.util.List; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.IAnnotationBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.java.tree.JavaType.FullyQualified; +import org.openrewrite.java.tree.TypeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.Annotations; @@ -33,8 +32,8 @@ public class RestrictedDefaultSymbolProvider extends AbstractSymbolProvider { private static final Logger log = LoggerFactory.getLogger(RestrictedDefaultSymbolProvider.class); @Override - protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, - Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + protected void addSymbolsPass1(Annotation node, FullyQualified typeBinding, + Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { // provide default symbol only in case this annotation is not combined with @Bean annotation if (!isCombinedWithAnnotation(node, Annotations.BEAN)) { @@ -48,21 +47,15 @@ public class RestrictedDefaultSymbolProvider extends AbstractSymbolProvider { } private boolean isCombinedWithAnnotation(Annotation node, String annotation) { - ASTNode parent = node.getParent(); + J parent = ORAstUtils.getParent(node); if (parent instanceof MethodDeclaration) { MethodDeclaration method = (MethodDeclaration) parent; - List modifiers = method.modifiers(); - for (Object modifier : modifiers) { - if (modifier instanceof Annotation) { - Annotation anno = (Annotation) modifier; - IAnnotationBinding annotationBinding = anno.resolveAnnotationBinding(); - String type = annotationBinding.getAnnotationType().getBinaryName(); - - if (type != null && type.equals(annotation)) { - return true; - } + for (Annotation a : method.getLeadingAnnotations()) { + FullyQualified otherAnnotationType = TypeUtils.asFullyQualified(a.getType()); + if (otherAnnotationType != null && annotation.equals(otherAnnotationType.getFullyQualifiedName())) { + return true; } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index 652fd6ad5..65b5a1cc9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.net.URI; @@ -31,20 +32,19 @@ import java.util.stream.Stream; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.ASTVisitor; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.FileASTRequestor; -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.NormalAnnotation; -import org.eclipse.jdt.core.dom.SingleMemberAnnotation; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.SymbolInformation; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaParser.Builder; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.ClassDeclaration; +import org.openrewrite.java.tree.J.CompilationUnit; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.java.tree.JavaType.FullyQualified; +import org.openrewrite.java.tree.TypeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; @@ -191,22 +191,20 @@ public class SpringIndexerJava implements SpringIndexer { } private void scanFile(IJavaProject project, DocumentDescriptor updatedDoc, String content) throws Exception { - ASTParser parser = createParser(project, false); + JavaParser parser = createParser(project, false); String docURI = updatedDoc.getDocURI(); long lastModified = updatedDoc.getLastModified(); + Path path = new File(new URI(docURI)).toPath(); if (content == null) { - Path path = new File(new URI(docURI)).toPath(); content = new String(Files.readAllBytes(path)); } String unitName = docURI.substring(docURI.lastIndexOf("/")); - parser.setUnitName(unitName); log.debug("Scan file: {}", unitName); - parser.setSource(content.toCharArray()); - - CompilationUnit cu = (CompilationUnit) parser.createAST(null); + byte[] contentBytes = content.getBytes(); + CompilationUnit cu = ORAstUtils.parseInputs(parser, List.of(new Parser.Input(path, () -> new ByteArrayInputStream(contentBytes)))).get(0); if (cu != null) { List generatedSymbols = new ArrayList(); @@ -232,7 +230,7 @@ public class SpringIndexerJava implements SpringIndexer { } private Set scanFilesInternally(IJavaProject project, DocumentDescriptor[] docs) throws Exception { - ASTParser parser = createParser(project, false); + JavaParser parser = createParser(project, false); // this is to keep track of already scanned files to avoid endless loops due to circular dependencies Set scannedTypes = new HashSet<>(); @@ -253,31 +251,30 @@ public class SpringIndexerJava implements SpringIndexer { List generatedSymbols = new ArrayList(); Multimap dependencies = MultimapBuilder.hashKeys().hashSetValues().build(); - FileASTRequestor requestor = new FileASTRequestor() { - @Override - public void acceptAST(String sourceFilePath, CompilationUnit cu) { - File file = new File(sourceFilePath); - String docURI = UriUtil.toUri(file).toString(); - - DocumentDescriptor updatedDoc = updatedDocs.get(docURI); - long lastModified = updatedDoc.getLastModified(); - - AtomicReference docRef = new AtomicReference<>(); - - SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, - lastModified, docRef, null, generatedSymbols, SCAN_PASS.ONE, new ArrayList<>()); - - scanAST(context); - - dependencies.putAll(sourceFilePath, context.getDependencies()); - scannedTypes.addAll(context.getScannedTypes()); - - fileScannedEvent(sourceFilePath); - } - }; - - parser.createASTs(javaFiles, null, new String[0], requestor, null); + Collection javaPaths = Arrays.stream(javaFiles).map(p -> Path.of(p)).collect(Collectors.toList()); + List cus = ORAstUtils.parse(parser, javaPaths); + for (CompilationUnit cu : cus) { + File file = cu.getSourcePath().toFile(); + String sourceFilePath = file.getPath(); + String docURI = UriUtil.toUri(file).toString(); + + DocumentDescriptor updatedDoc = updatedDocs.get(docURI); + long lastModifiedStamp = updatedDoc.getLastModified(); + + AtomicReference docRef = new AtomicReference<>(); + + SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, + lastModifiedStamp, docRef, null, generatedSymbols, SCAN_PASS.ONE, new ArrayList<>()); + + scanAST(context); + + dependencies.putAll(sourceFilePath, context.getDependencies()); + scannedTypes.addAll(context.getScannedTypes()); + + fileScannedEvent(sourceFilePath); + } + for (CachedSymbol symbol : generatedSymbols) { symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol()); } @@ -361,96 +358,69 @@ public class SpringIndexerJava implements SpringIndexer { private String[] scanFiles(IJavaProject project, String[] javaFiles, List generatedSymbols, SCAN_PASS pass) throws Exception { - ASTParser parser = createParser(project, SCAN_PASS.ONE.equals(pass)); + JavaParser parser = createParser(project, SCAN_PASS.ONE.equals(pass)); List nextPassFiles = new ArrayList<>(); + + Collection javaPaths = Arrays.stream(javaFiles).map(p -> Path.of(p)).collect(Collectors.toList()); + List cus = ORAstUtils.parse(parser, javaPaths); - FileASTRequestor requestor = new FileASTRequestor() { - @Override - public void acceptAST(String sourceFilePath, CompilationUnit cu) { - File file = new File(sourceFilePath); - String docURI = UriUtil.toUri(file).toString(); - long lastModified = file.lastModified(); - AtomicReference docRef = new AtomicReference<>(); + for (CompilationUnit cu : cus) { + File file = cu.getSourcePath().toFile(); + String sourceFilePath = file.getPath(); + String docURI = UriUtil.toUri(file).toString(); + long lastModified = file.lastModified(); + AtomicReference docRef = new AtomicReference<>(); - SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, - lastModified, docRef, null, generatedSymbols, pass, nextPassFiles); + SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, + lastModified, docRef, null, generatedSymbols, pass, nextPassFiles); - scanAST(context); - } - }; - - parser.createASTs(javaFiles, null, new String[0], requestor, null); + scanAST(context); + } return (String[]) nextPassFiles.toArray(new String[nextPassFiles.size()]); } private void scanAST(final SpringIndexerJavaContext context) { - context.getCu().accept(new ASTVisitor() { - - @Override - public boolean visit(TypeDeclaration node) { + new JavaIsoVisitor() { + + public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, SpringIndexerJavaContext indexer) { try { - context.addScannedType(node.resolveBinding()); - extractSymbolInformation(node, context); + context.addScannedType(classDecl.getType()); + extractSymbolInformation(classDecl, indexer); } catch (Exception e) { - log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); + log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + classDecl.getSimpleName(), e); } - return super.visit(node); + return super.visitClassDeclaration(classDecl, indexer); } - - @Override - public boolean visit(MethodDeclaration node) { + + public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, SpringIndexerJavaContext indexer) { try { - extractSymbolInformation(node, context); + extractSymbolInformation(method, indexer); } catch (Exception e) { - log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); + log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + method.getSimpleName(), e); } - return super.visit(node); + return super.visitMethodDeclaration(method, indexer); } - - @Override - public boolean visit(SingleMemberAnnotation node) { + + public Annotation visitAnnotation(Annotation annotation, SpringIndexerJavaContext indexer) { try { - extractSymbolInformation(node, context); + extractSymbolInformation(annotation, context); } catch (Exception e) { - log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); + log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + annotation.printTrimmed(), e); } - - return super.visit(node); + return super.visitAnnotation(annotation, context); } - - @Override - public boolean visit(NormalAnnotation node) { - try { - extractSymbolInformation(node, context); - } - catch (Exception e) { - log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); - } - - return super.visit(node); - } - - @Override - public boolean visit(MarkerAnnotation node) { - try { - extractSymbolInformation(node, context); - } - catch (Exception e) { - log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); - } - - return super.visit(node); - } - }); + + }.visitNonNull(context.getCu(), context); + dependencyTracker.update(context.getFile(), context.getDependencies());; } - private void extractSymbolInformation(TypeDeclaration typeDeclaration, final SpringIndexerJavaContext context) throws Exception { + private void extractSymbolInformation(ClassDeclaration typeDeclaration, final SpringIndexerJavaContext context) throws Exception { Collection providers = symbolProviders.getAll(); if (!providers.isEmpty()) { TextDocument doc = DocumentUtils.getTempTextDocument(context.getDocURI(), context.getDocRef(), context.getContent()); @@ -471,16 +441,16 @@ public class SpringIndexerJava implements SpringIndexer { } private void extractSymbolInformation(Annotation node, final SpringIndexerJavaContext context) throws Exception { - ITypeBinding typeBinding = node.resolveTypeBinding(); + FullyQualified type = TypeUtils.asFullyQualified(node.getType()); - if (typeBinding != null) { - Collection providers = symbolProviders.get(typeBinding); - Collection metaAnnotations = AnnotationHierarchies.getMetaAnnotations(typeBinding, symbolProviders::containsKey); + if (type != null) { + Collection providers = symbolProviders.get(type); + Collection metaAnnotations = AnnotationHierarchies.getMetaAnnotations(type, symbolProviders::containsKey); if (!providers.isEmpty()) { TextDocument doc = DocumentUtils.getTempTextDocument(context.getDocURI(), context.getDocRef(), context.getContent()); for (SymbolProvider provider : providers) { - provider.addSymbols(node, typeBinding, metaAnnotations, context, doc); + provider.addSymbols(node, type, metaAnnotations, context, doc); } } else { SymbolInformation symbol = provideDefaultSymbol(node, context); @@ -497,9 +467,9 @@ public class SpringIndexerJava implements SpringIndexer { private SymbolInformation provideDefaultSymbol(Annotation node, final SpringIndexerJavaContext context) { try { - ITypeBinding type = node.resolveTypeBinding(); + FullyQualified type = TypeUtils.asFullyQualified(node.getType()); if (type != null) { - String qualifiedName = type.getQualifiedName(); + String qualifiedName = type.getFullyQualifiedName(); if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) { TextDocument doc = DocumentUtils.getTempTextDocument(context.getDocURI(), context.getDocRef(), context.getContent()); return DefaultSymbolProvider.provideDefaultSymbol(node, doc); @@ -513,31 +483,20 @@ public class SpringIndexerJava implements SpringIndexer { return null; } - private ASTParser createParser(IJavaProject project, boolean ignoreMethodBodies) throws Exception { - String[] classpathEntries = getClasspathEntries(project); - String[] sourceEntries = getSourceEntries(project); - - ASTParser parser = ASTParser.newParser(AST.JLS16); - Map options = JavaCore.getOptions(); - JavaCore.setComplianceOptions(JavaCore.VERSION_16, options); - parser.setCompilerOptions(options); - parser.setKind(ASTParser.K_COMPILATION_UNIT); - parser.setStatementsRecovery(true); - parser.setBindingsRecovery(true); - parser.setResolveBindings(true); - parser.setIgnoreMethodBodies(ignoreMethodBodies); - - parser.setEnvironment(classpathEntries, sourceEntries, null, false); - return parser; + private JavaParser createParser(IJavaProject project, boolean ignoreMethodBodies) throws Exception { + JavaParser jp = JavaParser.fromJavaVersion().build(); + jp.setClasspath(getClasspathEntries(project)); + return jp; } - private String[] getClasspathEntries(IJavaProject project) throws Exception { + private Collection getClasspathEntries(IJavaProject project) throws Exception { IClasspath classpath = project.getClasspath(); Stream classpathEntries = IClasspathUtil.getAllBinaryRoots(classpath).stream(); return classpathEntries .filter(file -> file.exists()) - .map(file -> file.getAbsolutePath()) - .toArray(String[]::new); + .map(file -> file.getAbsoluteFile()) + .map(file -> file.toPath()) + .collect(Collectors.toSet()); } private String[] getSourceEntries(IJavaProject project) throws Exception { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java index 74bf5621f..6ad76e03c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java @@ -15,8 +15,9 @@ import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.openrewrite.java.tree.J.CompilationUnit; +import org.openrewrite.java.tree.JavaType.FullyQualified; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -123,9 +124,9 @@ public class SpringIndexerJavaContext { return scannedTypes; } - public void addScannedType(ITypeBinding scannedType) { + public void addScannedType(FullyQualified scannedType) { if (scannedType != null) { - String type = scannedType.getKey(); + String type = scannedType.getFullyQualifiedName(); scannedTypes.add(type); dependencies.remove(type); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java index 0dfd54d38..ebfa76dc8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2019 Pivotal, Inc. + * Copyright (c) 2017, 2022 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 @@ -18,18 +18,16 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.eclipse.jdt.core.dom.ASTNode; -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.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.CodeLens; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.Assignment; +import org.openrewrite.java.tree.J.ClassDeclaration; +import org.openrewrite.java.tree.J.Literal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; @@ -37,6 +35,7 @@ import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils; import org.springframework.ide.vscode.boot.java.livehover.v2.LiveProperties; import org.springframework.ide.vscode.boot.java.livehover.v2.LiveProperty; import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveData; +import org.springframework.ide.vscode.boot.java.utils.ORAstUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Renderable; @@ -59,16 +58,18 @@ public class ValueHoverProvider implements HoverProvider { @Override - public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, + public Hover provideHover(J node, Annotation annotation, int offset, TextDocument doc, IJavaProject project, SpringProcessLiveData[] processLiveData) { try { - ASTNode foundNode = NodeFinder.perform(node, offset, 0); - ASTNode exactNode = getExactNode(foundNode); + if (node instanceof Literal) { + Literal literal = (Literal) node; - if (exactNode != null) { - return provideHover(exactNode.toString(), offset - exactNode.getStartPosition(), - exactNode.getStartPosition(), doc, processLiveData); + if (isProperLiteral(literal, annotation)) { + org.openrewrite.marker.Range r = ORAstUtils.getRange(literal); + return provideHover(literal.printTrimmed(), offset - r.getStart().getOffset(), + r.getStart().getOffset(), doc, processLiveData); + } } } catch (Exception e) { logger.error("Error while generating live hovers for @Value", e); @@ -77,23 +78,26 @@ public class ValueHoverProvider implements HoverProvider { return null; } - private ASTNode getExactNode(ASTNode exactNode) { - if (exactNode != null) { - // case: @Value("prefix<*>") - if (exactNode instanceof StringLiteral && exactNode.getParent() instanceof Annotation) { - if (exactNode.toString().startsWith("\"") && exactNode.toString().endsWith("\"")) { - return exactNode; - } + private boolean isProperLiteral(Literal l, Annotation a) { + // case: @Value("prefix<*>") + if (ORAstUtils.getParent(l) == a) { + String str = ORAstUtils.getLiteralValue(l); + if (str.startsWith("\"") && str.endsWith("\"")) { + return true; } - // case: @Value(value="prefix<*>") - else if (exactNode instanceof StringLiteral && exactNode.getParent() instanceof MemberValuePair - && "value".equals(((MemberValuePair) exactNode.getParent()).getName().toString())) { - if (exactNode.toString().startsWith("\"") && exactNode.toString().endsWith("\"")) { - return exactNode; + } + // case: @Value(value="prefix<*>") + else if (ORAstUtils.getParent(l) instanceof Assignment && ORAstUtils.getGrandParent(l) == a) { + Assignment assign = (Assignment) ORAstUtils.getParent(l); + if ("value".equals(assign.getVariable().printTrimmed())) { + String str = ORAstUtils.getLiteralValue(l); + if (str.startsWith("\"") && str.endsWith("\"")) { + return true; } } } - return null; + + return false; } private Hover provideHover(String value, int offset, int nodeStartOffset, TextDocument doc, SpringProcessLiveData[] processLiveData) { @@ -153,10 +157,7 @@ public class ValueHoverProvider implements HoverProvider { * @param processLiveData * @return */ - private List provideHighlightHints(TextDocument doc, StringLiteral node, SpringProcessLiveData[] processLiveData) { - ASTNode exactNode = getExactNode(node); - - if (exactNode != null) { + private List provideHighlightHints(TextDocument doc, Literal l, SpringProcessLiveData[] processLiveData) { Map> propertiesWithRanges = new HashMap<>(); // Get the escaped value that INCLUDES the quotes as we want to compute @@ -166,7 +167,7 @@ public class ValueHoverProvider implements HoverProvider { // to highlight a.prop we need to take into account the starting '"' after the // '=' // to get the correct range of a.prop - String nodeValue = node.getEscapedValue(); + String nodeValue = ORAstUtils.getLiteralValue(l); if (nodeValue != null) { // Get property names with ranges highlight propertiesWithRanges = parseProperties(nodeValue); @@ -188,7 +189,8 @@ public class ValueHoverProvider implements HoverProvider { propRanges.stream().forEach(propRange -> { try { - Range hoverRange = doc.toRange(exactNode.getStartPosition() + propRange.getStart(), + org.openrewrite.marker.Range r = ORAstUtils.getRange(l); + Range hoverRange = doc.toRange(r.getStart().getOffset() + propRange.getStart(), propRange.getEnd() - propRange.getStart()); lenses.add(new CodeLens(hoverRange)); } catch (BadLocationException e) { @@ -202,7 +204,6 @@ public class ValueHoverProvider implements HoverProvider { } return lenses; } - } return ImmutableList.of(); } @@ -360,7 +361,7 @@ public class ValueHoverProvider implements HoverProvider { } @Override - public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, + public Hover provideHover(J node, ClassDeclaration typeDeclaration, int offset, TextDocument doc, IJavaProject project, SpringProcessLiveData[] processLiveData) { return null; } @@ -370,15 +371,18 @@ public class ValueHoverProvider implements HoverProvider { // Show highlight hints for properties in @Value that have live information List lenses = new ArrayList<>(); - annotation.accept(new ASTVisitor() { - @Override - public boolean visit(StringLiteral node) { - List provideHighlightHints = provideHighlightHints(doc, node, processLiveData); - lenses.addAll(provideHighlightHints); - return super.visit(node); - } - }); - + + new JavaIsoVisitor>() { + + public Literal visitLiteral(Literal literal, List cl) { + if (isProperLiteral(literal, annotation)) { + cl.addAll(provideHighlightHints(doc, literal, processLiveData)); + } + return super.visitLiteral(literal, cl); + }; + + }.visitNonNull(annotation, lenses); + return lenses; }