GH-1431: replaced scan pass logic with exception handling and removed AbstractSymbolProvider

GH-1431: changed indexing of components to index embedded components like bean methods, request mappings, etc.

GH-1431: added new tests for index structure
Signed-off-by: Martin Lippert <martin.lippert@broadcom.com>
This commit is contained in:
Martin Lippert
2025-02-26 15:39:03 +01:00
parent 0f081b93ea
commit 025382bedb
49 changed files with 1837 additions and 874 deletions

View File

@@ -0,0 +1,166 @@
/*******************************************************************************
* Copyright (c) 2017, 2025 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.beans;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.lsp4j.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.FunctionUtils;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import reactor.util.function.Tuple2;
/**
* @author Martin Lippert
* @author Kris De Volder
*/
public class BeansIndexer {
private static final Logger log = LoggerFactory.getLogger(BeansIndexer.class);
public static void indexBeanMethod(Bean configBean, Annotation node, SpringIndexerJavaContext context, TextDocument doc) {
if (node == null) return;
ASTNode parent = node.getParent();
if (parent == null || !(parent instanceof MethodDeclaration)) return;
MethodDeclaration method = (MethodDeclaration) parent;
if (isMethodAbstract(method)) return;
boolean isWebfluxRouter = WebfluxRouterSymbolProvider.isWebfluxRouterBean(method);
if (isWebfluxRouter) {
if (!context.isFullAst()) {
throw new RequiredCompleteAstException();
}
}
boolean isFunction = isFunctionBean(method);
ITypeBinding beanType = getBeanType(method);
String markerString = getAnnotations(method);
for (Tuple2<String, DocumentRegion> nameAndRegion : BeanUtils.getBeanNamesFromBeanAnnotationWithRegions(node, doc)) {
try {
Location location = new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2()));
String beanLabel = beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString);
InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(method, doc);
Set<String> supertypes = new HashSet<>();
ASTUtils.findSupertypes(beanType, supertypes);
Collection<Annotation> annotationsOnMethod = ASTUtils.getAnnotations(method);
AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc);
Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false, beanLabel);
if (isWebfluxRouter) {
WebfluxRouterSymbolProvider.createWebfluxElements(beanDefinition, method, context, doc);
}
configBean.addChild(beanDefinition);
} catch (BadLocationException e) {
log.error("", e);
}
}
}
public static String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) {
StringBuilder symbolLabel = new StringBuilder();
symbolLabel.append('@');
symbolLabel.append(isFunctionBean ? '>' : '+');
symbolLabel.append(' ');
symbolLabel.append('\'');
symbolLabel.append(beanName);
symbolLabel.append('\'');
markerString = markerString != null && markerString.length() > 0 ? " (" + markerString + ") " : " ";
symbolLabel.append(markerString);
symbolLabel.append(beanType);
return symbolLabel.toString();
}
public static ITypeBinding getBeanType(MethodDeclaration method) {
return method.getReturnType2().resolveBinding();
}
public static boolean isFunctionBean(MethodDeclaration method) {
String returnType = null;
if (method.getReturnType2().isParameterizedType()) {
ParameterizedType paramType = (ParameterizedType) method.getReturnType2();
Type type = paramType.getType();
ITypeBinding typeBinding = type.resolveBinding();
returnType = typeBinding.getBinaryName();
}
else {
returnType = method.getReturnType2().resolveBinding().getQualifiedName();
}
return FunctionUtils.FUNCTION_FUNCTION_TYPE.equals(returnType) || FunctionUtils.FUNCTION_CONSUMER_TYPE.equals(returnType)
|| FunctionUtils.FUNCTION_SUPPLIER_TYPE.equals(returnType);
}
public static String getAnnotations(MethodDeclaration method) {
StringBuilder result = new StringBuilder();
List<?> modifiers = method.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof Annotation) {
Annotation annotation = (Annotation) modifier;
IAnnotationBinding annotationBinding = annotation.resolveAnnotationBinding();
String type = annotationBinding.getAnnotationType().getBinaryName();
if (type != null && !Annotations.BEAN.equals(type)) {
result.append(' ');
result.append(annotation.toString());
}
}
}
return result.toString();
}
public static boolean isMethodAbstract(MethodDeclaration method) {
List<?> modifiers = method.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof Modifier && ((Modifier) modifier).isAbstract()) {
return true;
}
}
return false;
}
}

View File

@@ -10,21 +10,14 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.beans;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolKind;
@@ -32,18 +25,14 @@ import org.eclipse.lsp4j.WorkspaceSymbol;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider;
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.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.FunctionUtils;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -54,7 +43,7 @@ import reactor.util.function.Tuple2;
* @author Martin Lippert
* @author Kris De Volder
*/
public class BeansSymbolProvider extends AbstractSymbolProvider {
public class BeansSymbolProvider implements SymbolProvider {
private static final Logger log = LoggerFactory.getLogger(BeansSymbolProvider.class);
@@ -66,68 +55,24 @@ public class BeansSymbolProvider extends AbstractSymbolProvider {
if (parent == null || !(parent instanceof MethodDeclaration)) return;
MethodDeclaration method = (MethodDeclaration) parent;
if (isMethodAbstract(method)) return;
if (BeansIndexer.isMethodAbstract(method)) return;
List<SpringIndexElement> childElements = new ArrayList<>();
boolean isWebfluxRouter = WebfluxRouterSymbolProvider.isWebfluxRouterBean(method);
// for webflux details, we need full method body ASTs
if (isWebfluxRouter) {
Block methodBody = method.getBody();
if ((methodBody == null || methodBody.statements() == null || methodBody.statements().size() == 0)
&& SCAN_PASS.ONE.equals(context.getPass())) {
context.getNextPassFiles().add(context.getFile());
return;
}
else {
WebfluxRouterSymbolProvider.createWebfluxElements(method, context, doc, childElements);
}
} else if (!isWebfluxRouter && SCAN_PASS.TWO.equals(context.getPass())) {
return;
}
boolean isFunction = isFunctionBean(method);
boolean isFunction = BeansIndexer.isFunctionBean(method);
ITypeBinding beanType = getBeanType(method);
String markerString = getAnnotations(method);
ITypeBinding beanType = BeansIndexer.getBeanType(method);
String markerString = BeansIndexer.getAnnotations(method);
// lookup parent config
SpringIndexElement configParent = findNearestConfigBean(context.getBeans(), doc.getUri());
for (Tuple2<String, DocumentRegion> nameAndRegion : BeanUtils.getBeanNamesFromBeanAnnotationWithRegions(node, doc)) {
try {
Location location = new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2()));
WorkspaceSymbol symbol = new WorkspaceSymbol(
beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString),
BeansIndexer.beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString),
SymbolKind.Interface,
Either.forLeft(location)
);
InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(method, doc);
Set<String> supertypes = new HashSet<>();
ASTUtils.findSupertypes(beanType, supertypes);
Collection<Annotation> annotationsOnMethod = ASTUtils.getAnnotations(method);
AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc);
Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false, symbol.getName());
if (childElements.size() > 0) {
for (SpringIndexElement springIndexElement : childElements) {
beanDefinition.addChild(springIndexElement);
}
}
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
if (configParent != null) {
configParent.addChild(beanDefinition);
}
else {
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
}
} catch (BadLocationException e) {
log.error("", e);
@@ -135,22 +80,12 @@ public class BeansSymbolProvider extends AbstractSymbolProvider {
}
}
private SpringIndexElement findNearestConfigBean(List<CachedBean> beans, String docURI) {
int i = beans.size() - 1;
while (i >= 0 && beans.get(i).getDocURI().equals(docURI)) {
if (beans.get(i).getBean() instanceof Bean bean && bean.isConfiguration() && docURI.equals(docURI)) {
return beans.get(i).getBean();
}
i--;
}
return null;
@Override
public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
indexFunctionBeans(typeDeclaration, context, doc);
}
@Override
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
// this checks function beans that are defined as implementations of Function interfaces
private void indexFunctionBeans(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
ITypeBinding functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc);
if (functionBean != null) {
try {
@@ -159,7 +94,7 @@ public class BeansSymbolProvider extends AbstractSymbolProvider {
Location beanLocation = new Location(doc.getUri(), doc.toRange(ASTUtils.nodeRegion(doc, typeDeclaration.getName())));
WorkspaceSymbol symbol = new WorkspaceSymbol(
beanLabel(true, beanName, beanType.getName(), null),
BeansIndexer.beanLabel(true, beanName, beanType.getName(), null),
SymbolKind.Interface,
Either.forLeft(beanLocation));
@@ -183,70 +118,4 @@ public class BeansSymbolProvider extends AbstractSymbolProvider {
}
}
public static String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) {
StringBuilder symbolLabel = new StringBuilder();
symbolLabel.append('@');
symbolLabel.append(isFunctionBean ? '>' : '+');
symbolLabel.append(' ');
symbolLabel.append('\'');
symbolLabel.append(beanName);
symbolLabel.append('\'');
markerString = markerString != null && markerString.length() > 0 ? " (" + markerString + ") " : " ";
symbolLabel.append(markerString);
symbolLabel.append(beanType);
return symbolLabel.toString();
}
protected ITypeBinding getBeanType(MethodDeclaration method) {
return method.getReturnType2().resolveBinding();
}
private boolean isFunctionBean(MethodDeclaration method) {
String returnType = null;
if (method.getReturnType2().isParameterizedType()) {
ParameterizedType paramType = (ParameterizedType) method.getReturnType2();
Type type = paramType.getType();
ITypeBinding typeBinding = type.resolveBinding();
returnType = typeBinding.getBinaryName();
}
else {
returnType = method.getReturnType2().resolveBinding().getQualifiedName();
}
return FunctionUtils.FUNCTION_FUNCTION_TYPE.equals(returnType) || FunctionUtils.FUNCTION_CONSUMER_TYPE.equals(returnType)
|| FunctionUtils.FUNCTION_SUPPLIER_TYPE.equals(returnType);
}
private String getAnnotations(MethodDeclaration method) {
StringBuilder result = new StringBuilder();
List<?> modifiers = method.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof Annotation) {
Annotation annotation = (Annotation) modifier;
IAnnotationBinding annotationBinding = annotation.resolveAnnotationBinding();
String type = annotationBinding.getAnnotationType().getBinaryName();
if (type != null && !Annotations.BEAN.equals(type)) {
result.append(' ');
result.append(annotation.toString());
}
}
}
return result.toString();
}
private boolean isMethodAbstract(MethodDeclaration method) {
List<?> modifiers = method.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof Modifier && ((Modifier) modifier).isAbstract()) {
return true;
}
}
return false;
}
}

View File

@@ -14,7 +14,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -34,9 +33,13 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement;
import org.springframework.ide.vscode.boot.java.events.EventListenerIndexer;
import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement;
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexer;
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.DefaultSymbolProvider;
@@ -53,12 +56,12 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
* @author Martin Lippert
* @author Kris De Volder
*/
public class ComponentSymbolProvider extends AbstractSymbolProvider {
public class ComponentSymbolProvider implements SymbolProvider {
private static final Logger log = LoggerFactory.getLogger(ComponentSymbolProvider.class);
@Override
protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
public void addSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
try {
if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration) {
createSymbol(node, annotationType, metaAnnotations, context, doc);
@@ -69,7 +72,7 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
context.getBeans().add(new CachedBean(context.getDocURI(), new SimpleSymbolElement(symbol)));
}
}
catch (Exception e) {
catch (BadLocationException e) {
log.error("", e);
}
}
@@ -111,30 +114,106 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
Bean beanDefinition = new Bean(beanName, beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, isConfiguration, symbol.getName());
// type implements event listener - move those already created event index elements under the bean node
List<CachedBean> alreadyCreatedEventListenerChilds = context.getBeans().stream()
.filter(cachedBean -> cachedBean.getDocURI().equals(doc.getUri()))
.filter(cachedBean -> cachedBean.getBean() instanceof EventListenerIndexElement)
.toList();
for (CachedBean eventListener : alreadyCreatedEventListenerChilds) {
context.getBeans().remove(eventListener);
beanDefinition.addChild(eventListener.getBean());
}
// event publisher checks
boolean usesEventPublisher = false;
for (InjectionPoint injectionPoint : injectionPoints) {
if (Annotations.EVENT_PUBLISHER.equals(injectionPoint.getType())) {
context.getNextPassFiles().add(context.getFile());
usesEventPublisher = true;
}
}
if (usesEventPublisher) {
if (context.isFullAst()) {
scanEventPublisherInvocations(beanDefinition, node, annotationType, metaAnnotations, context, doc);
}
else {
throw new RequiredCompleteAstException();
}
}
indexBeanMethods(beanDefinition, type, annotationType, metaAnnotations, context, doc);
indexEventListeners(beanDefinition, type, annotationType, metaAnnotations, context, doc);
indexEventListenerInterfaceImplementation(beanDefinition, type, context, doc);
indexRequestMappings(beanDefinition, type, annotationType, metaAnnotations, context, doc);
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
}
@Override
protected void addSymbolsPass2(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
private void indexBeanMethods(Bean bean, TypeDeclaration type, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type);
if (bean.isConfiguration()) {
MethodDeclaration[] methods = type.getMethods();
if (methods == null) {
return;
}
for (int i = 0; i < methods.length; i++) {
MethodDeclaration methodDecl = methods[i];
Collection<Annotation> annotations = ASTUtils.getAnnotations(methodDecl);
for (Annotation annotation : annotations) {
ITypeBinding typeBinding = annotation.resolveTypeBinding();
boolean isBeanMethod = annotationHierarchies.isAnnotatedWith(typeBinding, Annotations.BEAN);
if (isBeanMethod) {
BeansIndexer.indexBeanMethod(bean, annotation, context, doc);
}
}
}
}
}
private void indexEventListeners(Bean bean, TypeDeclaration type, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type);
MethodDeclaration[] methods = type.getMethods();
if (methods == null) {
return;
}
for (int i = 0; i < methods.length; i++) {
MethodDeclaration methodDecl = methods[i];
Collection<Annotation> annotations = ASTUtils.getAnnotations(methodDecl);
for (Annotation annotation : annotations) {
ITypeBinding typeBinding = annotation.resolveTypeBinding();
boolean isEventListenerAnnotation = annotationHierarchies.isAnnotatedWith(typeBinding, Annotations.EVENT_LISTENER);
if (isEventListenerAnnotation) {
EventListenerIndexer.indexEventListener(bean, annotation, context, doc);
}
}
}
}
private void indexRequestMappings(Bean controller, TypeDeclaration type, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type);
boolean isController = annotationHierarchies.isAnnotatedWith(annotationType, Annotations.CONTROLLER);
if (isController) {
MethodDeclaration[] methods = type.getMethods();
if (methods == null) {
return;
}
for (int i = 0; i < methods.length; i++) {
MethodDeclaration methodDecl = methods[i];
Collection<Annotation> annotations = ASTUtils.getAnnotations(methodDecl);
for (Annotation annotation : annotations) {
ITypeBinding typeBinding = annotation.resolveTypeBinding();
boolean isRequestMappingAnnotation = annotationHierarchies.isAnnotatedWith(typeBinding, Annotations.SPRING_REQUEST_MAPPING);
if (isRequestMappingAnnotation) {
RequestMappingIndexer.indexRequestMapping(controller, annotation, context, doc);
}
}
}
}
}
private void scanEventPublisherInvocations(Bean component, Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
TypeDeclaration type = (TypeDeclaration) node.getParent();
type.accept(new ASTVisitor() {
@@ -161,11 +240,8 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
ASTUtils.findSupertypes(eventTypeBinding, typesFromhierarchy);
EventPublisherIndexElement eventPublisherIndexElement = new EventPublisherIndexElement(eventTypeBinding.getQualifiedName(), location, typesFromhierarchy);
Bean publisherBeanElement = findBean(node, methodInvocation, context, doc);
if (publisherBeanElement != null) {
publisherBeanElement.addChild(eventPublisherIndexElement);
}
component.addChild(eventPublisherIndexElement);
// symbol
String symbolLabel = "@EventPublisher (" + eventTypeBinding.getName() + ")";
WorkspaceSymbol symbol = new WorkspaceSymbol(symbolLabel, SymbolKind.Interface, Either.forLeft(location));
@@ -184,7 +260,18 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
}
@Override
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(typeDeclaration);
boolean isComponment = annotationHierarchies.isAnnotatedWith(typeDeclaration.resolveBinding(), Annotations.COMPONENT);
// check for event listener implementations on classes that are not annotated with component, but created via bean methods (for example)
if (!isComponment) {
indexEventListenerInterfaceImplementation(null, typeDeclaration, context, doc);
}
}
private void indexEventListenerInterfaceImplementation(Bean bean, TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
try {
ITypeBinding typeBinding = typeDeclaration.resolveBinding();
if (typeBinding == null) return;
@@ -209,7 +296,13 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
AnnotationMetadata[] handleEventMethodAnnotations = ASTUtils.getAnnotationsMetadata(annotationsOnHandleEventMethod, doc);
EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, typeBinding.getQualifiedName(), handleEventMethodAnnotations);
context.getBeans().add(new CachedBean(doc.getUri(), eventElement));
if (bean != null) {
bean.addChild(eventElement);
}
else {
context.getBeans().add(new CachedBean(context.getDocURI(), eventElement));
}
}
} catch (BadLocationException e) {
log.error("", e);
@@ -230,24 +323,6 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
return null;
}
private Bean findBean(Annotation annotation, MethodInvocation methodInvocation, SpringIndexerJavaContext context, TextDocument doc) {
TypeDeclaration declaringType = ASTUtils.findDeclaringType(methodInvocation);
if (declaringType != null) {
String beanName = BeanUtils.getBeanNameFromComponentAnnotation(annotation, declaringType);
if (beanName != null) {
Optional<Bean> first = context.getBeans().stream().filter(cachedBean -> cachedBean.getDocURI().equals(doc.getUri()))
.map(cachedBean -> cachedBean.getBean())
.filter(bean -> bean instanceof Bean)
.map(bean -> (Bean) bean)
.filter(bean -> bean.getName().equals(beanName))
.findFirst();
return first.get();
}
}
return null;
}
protected String beanLabel(String searchPrefix, String annotationTypeName, Collection<String> metaAnnotationNames, String beanName, String beanType) {
StringBuilder symbolLabel = new StringBuilder();
symbolLabel.append("@");

View File

@@ -31,7 +31,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Tuple;
import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
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.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
@@ -41,13 +41,12 @@ import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
public class FeignClientSymbolProvider extends AbstractSymbolProvider {
public class FeignClientSymbolProvider implements SymbolProvider {
private static final Logger log = LoggerFactory.getLogger(FeignClientSymbolProvider.class);
@Override
protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations,
SpringIndexerJavaContext context, TextDocument doc) {
public void addSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
try {
if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration) {
Two<WorkspaceSymbol, Bean> result = createSymbol(node, annotationType, metaAnnotations, doc);
@@ -58,7 +57,7 @@ public class FeignClientSymbolProvider extends AbstractSymbolProvider {
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
}
}
catch (Exception e) {
catch (BadLocationException e) {
log.error("", e);
}
}

View File

@@ -25,7 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.beans.BeanUtils;
import org.springframework.ide.vscode.boot.java.beans.CachedBean;
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
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.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
@@ -42,12 +42,12 @@ import reactor.util.function.Tuples;
/**
* @author Martin Lippert
*/
public class DataRepositorySymbolProvider extends AbstractSymbolProvider {
public class DataRepositorySymbolProvider implements SymbolProvider {
private static final Logger log = LoggerFactory.getLogger(DataRepositorySymbolProvider.class);
@Override
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
// this checks spring data repository beans that are defined as extensions of the repository interface
Tuple4<String, ITypeBinding, String, DocumentRegion> repositoryBean = getRepositoryBean(typeDeclaration, doc);

View File

@@ -0,0 +1,116 @@
/*******************************************************************************
* Copyright (c) 2025 Broadcom
* 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.events;
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.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class EventListenerIndexer {
private static final Logger log = LoggerFactory.getLogger(EventListenerIndexer.class);
public static void indexEventListener(Bean component, Annotation node, SpringIndexerJavaContext context, TextDocument doc) {
if (node == null) return;
ASTNode parent = node.getParent();
if (parent == null || !(parent instanceof MethodDeclaration)) return;
MethodDeclaration method = (MethodDeclaration) parent;
try {
// String symbolLabel = createEventListenerSymbolLabel(node, method);
// index element for event listener
Collection<Annotation> annotationsOnMethod = ASTUtils.getAnnotations(method);
AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc);
ITypeBinding eventType = getEventType(node, method);
DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, method.getName());
Location location = new Location(doc.getUri(), nodeRegion.asRange());
String containerBeanType = null;
TypeDeclaration type = ASTUtils.findDeclaringType(method);
if (type != null) {
ITypeBinding binding = type.resolveBinding();
if (binding != null) {
containerBeanType = binding.getQualifiedName();
}
}
EventListenerIndexElement eventListenerIndexElement = new EventListenerIndexElement(eventType != null ? eventType.getQualifiedName() : "", location, containerBeanType, annotations);
component.addChild(eventListenerIndexElement);
} catch (BadLocationException e) {
log.error("", e);
}
}
public static String createEventListenerSymbolLabel(Annotation node, MethodDeclaration method) {
// event listener annotation type
String annotationTypeName = getAnnotationTypeName(node);
ITypeBinding eventType = getEventType(node, method);
if (annotationTypeName != null) {
return "@" + annotationTypeName + (eventType != null ? " (" + eventType.getName() + ")" : "");
}
else {
return node.toString();
}
}
public static ITypeBinding getEventType(Annotation node, MethodDeclaration method) {
List<?> parameters = method.parameters();
if (parameters != null && parameters.size() == 1) {
SingleVariableDeclaration param = (SingleVariableDeclaration) parameters.get(0);
IVariableBinding paramBinding = param.resolveBinding();
if (paramBinding != null) {
ITypeBinding paramType = paramBinding.getType();
return paramType != null ? paramType : null;
}
}
return null;
}
public static String getAnnotationTypeName(Annotation node) {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null) {
return typeBinding.getName();
}
else {
return null;
}
}
}

View File

@@ -9,41 +9,34 @@
* Broadcom - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.events;
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.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.WorkspaceSymbol;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.beans.CachedBean;
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.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class EventListenerSymbolProvider extends AbstractSymbolProvider {
public class EventListenerSymbolProvider implements SymbolProvider {
private static final Logger log = LoggerFactory.getLogger(EventListenerSymbolProvider.class);
@Override
protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
if (node == null) return;
ASTNode parent = node.getParent();
@@ -51,89 +44,17 @@ public class EventListenerSymbolProvider extends AbstractSymbolProvider {
MethodDeclaration method = (MethodDeclaration) parent;
// symbol
try {
String symbolLabel = createEventListenerSymbolLabel(node, method);
String symbolLabel = EventListenerIndexer.createEventListenerSymbolLabel(node, method);
WorkspaceSymbol symbol = new WorkspaceSymbol(symbolLabel, SymbolKind.Interface,
Either.forLeft(new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()))));
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
// index element for event listener
Collection<Annotation> annotationsOnMethod = ASTUtils.getAnnotations(method);
AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc);
List<CachedBean> beans = context.getBeans();
if (beans.size() > 0 ) {
CachedBean cachedBean = beans.get(beans.size() - 1);
if (cachedBean.getDocURI().equals(doc.getUri())) {
ITypeBinding eventType = getEventType(node, method);
DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, method.getName());
Location location = new Location(doc.getUri(), nodeRegion.asRange());
String containerBeanType = null;
TypeDeclaration type = ASTUtils.findDeclaringType(method);
if (type != null) {
ITypeBinding binding = type.resolveBinding();
if (binding != null) {
containerBeanType = binding.getQualifiedName();
}
}
cachedBean.getBean().addChild(new EventListenerIndexElement(eventType != null ? eventType.getQualifiedName() : "", location, containerBeanType, annotations));
}
}
} catch (BadLocationException e) {
}
catch (BadLocationException e) {
log.error("", e);
}
}
@Override
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
super.addSymbolsPass1(typeDeclaration, context, doc);
}
private String createEventListenerSymbolLabel(Annotation node, MethodDeclaration method) {
// event listener annotation type
String annotationTypeName = getAnnotationTypeName(node);
ITypeBinding eventType = getEventType(node, method);
if (annotationTypeName != null) {
return "@" + annotationTypeName + (eventType != null ? " (" + eventType.getName() + ")" : "");
}
else {
return node.toString();
}
}
private ITypeBinding getEventType(Annotation node, MethodDeclaration method) {
List<?> parameters = method.parameters();
if (parameters != null && parameters.size() == 1) {
SingleVariableDeclaration param = (SingleVariableDeclaration) parameters.get(0);
IVariableBinding paramBinding = param.resolveBinding();
if (paramBinding != null) {
ITypeBinding paramType = paramBinding.getType();
return paramType != null ? paramType : null;
}
}
return null;
}
private String getAnnotationTypeName(Annotation node) {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null) {
return typeBinding.getName();
}
else {
return null;
}
}
}

View File

@@ -1,81 +0,0 @@
/*******************************************************************************
* Copyright (c) 2019 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
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.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;
/**
* @author Martin Lippert
*/
public class AbstractSymbolProvider implements SymbolProvider {
@Override
public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
if (SCAN_PASS.ONE.equals(context.getPass())) {
addSymbolsPass1(node, typeBinding, metaAnnotations, context, doc);
}
else if (SCAN_PASS.TWO.equals(context.getPass())) {
addSymbolsPass2(node, typeBinding, metaAnnotations, context, doc);
}
}
@Override
public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
if (SCAN_PASS.ONE.equals(context.getPass())) {
addSymbolsPass1(typeDeclaration, context, doc);
}
else if (SCAN_PASS.TWO.equals(context.getPass())) {
addSymbolsPass2(typeDeclaration, context, doc);
}
}
@Override
public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
if (SCAN_PASS.ONE.equals(context.getPass())) {
addSymbolsPass1(methodDeclaration, context, doc);
}
else if (SCAN_PASS.TWO.equals(context.getPass())) {
addSymbolsPass2(methodDeclaration, context, doc);
}
}
//
// 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<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
}
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
}
protected void addSymbolsPass1(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
}
protected void addSymbolsPass2(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
}
protected void addSymbolsPass2(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
}
protected void addSymbolsPass2(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2019 Pivotal, Inc.
* Copyright (c) 2017, 2025 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
@@ -25,8 +25,8 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
*/
public interface SymbolProvider {
void addSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc);
void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc);
void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc);
default void addSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {};
default void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {};
default void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {};
}

View File

@@ -0,0 +1,310 @@
/*******************************************************************************
* Copyright (c) 2017, 2025 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
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.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
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.eclipse.lsp4j.WorkspaceSymbol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class RequestMappingIndexer {
private static final Set<String> ATTRIBUTE_NAME_VALUE_PATH = Set.of("value", "path");
private static final Set<String> ATTRIBUTE_NAME_METHOD = Set.of("method");
private static final Set<String> ATTRIBUTE_NAME_CONSUMES = Set.of("consumes");
private static final Set<String> ATTRIBUTE_NAME_PRODUCES = Set.of("produces");
private static final Logger log = LoggerFactory.getLogger(RequestMappingIndexer.class);
public static void indexRequestMapping(Bean controller, Annotation node, SpringIndexerJavaContext context, TextDocument doc) {
if (node.getParent() instanceof MethodDeclaration) {
try {
Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()));
String[] path = getPath(node, context);
String[] parentPath = getParentPath(node, context);
String[] methods = getMethod(node, context);
String[] contentTypes = getContentTypes(node, context);
String[] acceptTypes = getAcceptTypes(node, context);
Stream<String> stream = parentPath == null ? Stream.of("") : Arrays.stream(parentPath);
stream.filter(Objects::nonNull)
.flatMap(parent -> (path == null ? Stream.<String>empty() : Arrays.stream(path))
.filter(Objects::nonNull).map(p -> {
return combinePath(parent, p);
}))
.forEach(p -> {
// symbol
WorkspaceSymbol symbol = RouteUtils.createRouteSymbol(location, p, methods, contentTypes, acceptTypes);
// index element for request mapping
RequestMappingIndexElement requestMappingIndexElement = new RequestMappingIndexElement(p, methods, contentTypes, acceptTypes, location.getRange(), symbol.getName());
controller.addChild(requestMappingIndexElement);
});
} catch (Exception e) {
log.error("problem occured while scanning for request mapping symbols from " + doc.getUri(), e);
}
}
}
public static String combinePath(String parent, String path) {
String separator = !parent.endsWith("/") && !path.startsWith("/") && !path.isEmpty() ? "/" : "";
String resultPath = parent + separator + path;
String result = resultPath.startsWith("/") ? resultPath : "/" + resultPath;
return result;
}
public static String[] getPath(Annotation node, SpringIndexerJavaContext context) {
String[] result = getAttributeValuesFromAnnotation(node, context, ATTRIBUTE_NAME_VALUE_PATH);
if (result == null && node.isSingleMemberAnnotation()) {
SingleMemberAnnotation singleNode = (SingleMemberAnnotation) node;
Expression expression = singleNode.getValue();
result = ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
}
return result != null ? result : new String[] { "" };
}
public static String[] getParentPath(Annotation node, SpringIndexerJavaContext context) {
Annotation parentAnnotation = getAnnotationFromClassLevel(node);
if (parentAnnotation != null) {
return getPath(parentAnnotation, context);
}
else {
return getAttributeValuesFromSupertypes(node, context, ATTRIBUTE_NAME_VALUE_PATH);
}
}
public static String[] getAcceptTypes(Annotation node, SpringIndexerJavaContext context) {
return getAttributeValues(node, context, ATTRIBUTE_NAME_CONSUMES);
}
public static String[] getContentTypes(Annotation node, SpringIndexerJavaContext context) {
return getAttributeValues(node, context, ATTRIBUTE_NAME_PRODUCES);
}
public static String[] getMethod(Annotation node, SpringIndexerJavaContext context) {
// extract from annotation type
String[] methods = getRequestMethod(node);
// extract from annotation params
if (methods == null) {
methods = getAttributeValues(node, context, ATTRIBUTE_NAME_METHOD);
}
return methods;
}
private static String[] getRequestMethod(Annotation annotation) {
ITypeBinding type = annotation.resolveTypeBinding();
if (type != null) {
switch (type.getQualifiedName()) {
case Annotations.SPRING_GET_MAPPING:
return new String[] { "GET" };
case Annotations.SPRING_POST_MAPPING:
return new String[] { "POST" };
case Annotations.SPRING_DELETE_MAPPING:
return new String[] { "DELETE" };
case Annotations.SPRING_PUT_MAPPING:
return new String[] { "PUT" };
case Annotations.SPRING_PATCH_MAPPING:
return new String[] { "PATCH" };
}
}
return null;
}
private static String[] getAttributeValues(Annotation node, SpringIndexerJavaContext context, Set<String> attributeNames) {
String[] result = getAttributeValuesFromAnnotation(node, context, attributeNames);
// extract from parent annotations
if (result == null) {
Annotation parentAnnotation = getAnnotationFromClassLevel(node);
if (parentAnnotation != null) {
result = getAttributeValuesFromAnnotation(parentAnnotation, context, attributeNames);
}
else {
result = getAttributeValuesFromSupertypes(node, context, attributeNames);
}
}
return result;
}
private static String[] getAttributeValuesFromAnnotation(Annotation node, SpringIndexerJavaContext context, Set<String> attributeNames) {
if (node.isNormalAnnotation()) {
NormalAnnotation normNode = (NormalAnnotation) node;
List<?> values = normNode.values();
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
Object object = iterator.next();
if (object instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) object;
String valueName = pair.getName().getIdentifier();
if (valueName != null && attributeNames.contains(valueName)) {
Expression expression = pair.getValue();
return ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
}
}
}
}
return null;
}
private static String[] getAttributeValuesFromSupertypes(Annotation node, SpringIndexerJavaContext context, Set<String> attributeNames) {
IAnnotationBinding annotationBinding = getAnnotationFromSupertypes(node, context);
IMemberValuePairBinding valuePair = getValuePair(annotationBinding, attributeNames);
ASTUtils.MemberValuePairAndType result = ASTUtils.getValuesFromValuePair(valuePair);
if (result != null) {
if (result.dereferencedType != null) {
context.addDependency(result.dereferencedType);
}
return result.values;
}
else {
return null;
}
}
private static IMemberValuePairBinding getValuePair(IAnnotationBinding annotationBinding, Set<String> names) {
if (annotationBinding != null) {
IMemberValuePairBinding[] valuePairs = annotationBinding.getDeclaredMemberValuePairs();
if (valuePairs != null ) {
for (int j = 0; j < valuePairs.length; j++) {
String valueName = valuePairs[j].getName();
if (valueName != null && names.contains(valueName)) {
return valuePairs[j];
}
}
}
}
return null;
}
private static Annotation getAnnotationFromClassLevel(Annotation node) {
// lookup class level request mapping annotation
ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null;
while (parent != null && !(parent instanceof TypeDeclaration)) {
parent = parent.getParent();
}
if (parent != null) {
TypeDeclaration type = (TypeDeclaration) parent;
List<?> modifiers = type.modifiers();
Iterator<?> iterator = modifiers.iterator();
while (iterator.hasNext()) {
Object modifier = iterator.next();
if (modifier instanceof Annotation) {
Annotation annotation = (Annotation) modifier;
ITypeBinding resolvedType = annotation.resolveTypeBinding();
String annotationType = resolvedType.getQualifiedName();
if (annotationType != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotationType)) {
return annotation;
}
}
}
}
return null;
}
private static IAnnotationBinding getAnnotationFromSupertypes(Annotation node, SpringIndexerJavaContext context) {
ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null;
while (parent != null && !(parent instanceof TypeDeclaration)) {
parent = parent.getParent();
}
if (parent != null) {
TypeDeclaration type = (TypeDeclaration) parent;
ITypeBinding typeBinding = type.resolveBinding();
if (typeBinding != null) {
return findFirstRequestMappingAnnotation(typeBinding);
}
}
return null;
}
private static IAnnotationBinding findFirstRequestMappingAnnotation(ITypeBinding start) {
if (start == null) {
return null;
}
IAnnotationBinding found = getRequestMappingAnnotation(start);
if (found != null) {
return found;
}
else {
// search interfaces first
ITypeBinding[] interfaces = start.getInterfaces();
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
found = findFirstRequestMappingAnnotation(interfaces[i]);
if (found != null) {
return found;
}
}
}
// search superclass second
ITypeBinding superclass = start.getSuperclass();
if (superclass != null) {
return findFirstRequestMappingAnnotation(superclass);
}
// nothing found
return null;
}
}
private static IAnnotationBinding getRequestMappingAnnotation(ITypeBinding typeBinding) {
IAnnotationBinding[] annotations = typeBinding.getAnnotations();
for (int i = 0; i < annotations.length; i++) {
if (annotations[i].getAnnotationType() != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotations[i].getAnnotationType().getQualifiedName())) {
return annotations[i];
}
}
return null;
}
}

View File

@@ -12,329 +12,57 @@ package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
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.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
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.eclipse.lsp4j.WorkspaceSymbol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.beans.CachedBean;
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.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
private static final Set<String> ATTRIBUTE_NAME_VALUE_PATH = Set.of("value", "path");
private static final Set<String> ATTRIBUTE_NAME_METHOD = Set.of("method");
private static final Set<String> ATTRIBUTE_NAME_CONSUMES = Set.of("consumes");
private static final Set<String> ATTRIBUTE_NAME_PRODUCES = Set.of("produces");
public class RequestMappingSymbolProvider implements SymbolProvider {
private static final Logger log = LoggerFactory.getLogger(RequestMappingSymbolProvider.class);
@Override
protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
public void addSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
if (node.getParent() instanceof MethodDeclaration) {
try {
Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()));
String[] path = getPath(node, context);
String[] parentPath = getParentPath(node, context);
String[] methods = getMethod(node, context);
String[] contentTypes = getContentTypes(node, context);
String[] acceptTypes = getAcceptTypes(node, context);
String[] path = RequestMappingIndexer.getPath(node, context);
String[] parentPath = RequestMappingIndexer.getParentPath(node, context);
String[] methods = RequestMappingIndexer.getMethod(node, context);
String[] contentTypes = RequestMappingIndexer.getContentTypes(node, context);
String[] acceptTypes = RequestMappingIndexer.getAcceptTypes(node, context);
Stream<String> stream = parentPath == null ? Stream.of("") : Arrays.stream(parentPath);
stream.filter(Objects::nonNull)
.flatMap(parent -> (path == null ? Stream.<String>empty() : Arrays.stream(path))
.filter(Objects::nonNull).map(p -> {
return combinePath(parent, p);
return RequestMappingIndexer.combinePath(parent, p);
}))
.forEach(p -> {
// symbol
WorkspaceSymbol symbol = RouteUtils.createRouteSymbol(location, p, methods, contentTypes, acceptTypes);
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
// index element for request mapping
SpringIndexElement parent = getPotentialParentIndexNode(context);
if (parent != null) {
parent.addChild(new RequestMappingIndexElement(p, methods, contentTypes, acceptTypes, location.getRange(), symbol.getName()));
}
else {
context.getBeans().add(new CachedBean(doc.getUri(), new RequestMappingIndexElement(p, methods, contentTypes, acceptTypes, location.getRange(), symbol.getName())));
}
});
} catch (Exception e) {
} catch (BadLocationException e) {
log.error("problem occured while scanning for request mapping symbols from " + doc.getUri(), e);
}
}
}
private SpringIndexElement getPotentialParentIndexNode(SpringIndexerJavaContext context) {
List<CachedBean> beans = context.getBeans();
for (int i = beans.size() - 1; i >= 0; i--) {
CachedBean cachedBean = beans.get(i);
if (!cachedBean.getDocURI().equals(context.getDocURI())) {
return null;
}
if (cachedBean.getBean() instanceof Bean bean) {
return bean;
}
}
return null;
}
private String combinePath(String parent, String path) {
String separator = !parent.endsWith("/") && !path.startsWith("/") && !path.isEmpty() ? "/" : "";
String resultPath = parent + separator + path;
String result = resultPath.startsWith("/") ? resultPath : "/" + resultPath;
return result;
}
private String[] getPath(Annotation node, SpringIndexerJavaContext context) {
String[] result = getAttributeValuesFromAnnotation(node, context, ATTRIBUTE_NAME_VALUE_PATH);
if (result == null && node.isSingleMemberAnnotation()) {
SingleMemberAnnotation singleNode = (SingleMemberAnnotation) node;
Expression expression = singleNode.getValue();
result = ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
}
return result != null ? result : new String[] { "" };
}
private String[] getParentPath(Annotation node, SpringIndexerJavaContext context) {
Annotation parentAnnotation = getAnnotationFromClassLevel(node);
if (parentAnnotation != null) {
return getPath(parentAnnotation, context);
}
else {
return getAttributeValuesFromSupertypes(node, context, ATTRIBUTE_NAME_VALUE_PATH);
}
}
private String[] getAcceptTypes(Annotation node, SpringIndexerJavaContext context) {
return getAttributeValues(node, context, ATTRIBUTE_NAME_CONSUMES);
}
private String[] getContentTypes(Annotation node, SpringIndexerJavaContext context) {
return getAttributeValues(node, context, ATTRIBUTE_NAME_PRODUCES);
}
private String[] getMethod(Annotation node, SpringIndexerJavaContext context) {
// extract from annotation type
String[] methods = getRequestMethod(node);
// extract from annotation params
if (methods == null) {
methods = getAttributeValues(node, context, ATTRIBUTE_NAME_METHOD);
}
return methods;
}
private String[] getRequestMethod(Annotation annotation) {
ITypeBinding type = annotation.resolveTypeBinding();
if (type != null) {
switch (type.getQualifiedName()) {
case Annotations.SPRING_GET_MAPPING:
return new String[] { "GET" };
case Annotations.SPRING_POST_MAPPING:
return new String[] { "POST" };
case Annotations.SPRING_DELETE_MAPPING:
return new String[] { "DELETE" };
case Annotations.SPRING_PUT_MAPPING:
return new String[] { "PUT" };
case Annotations.SPRING_PATCH_MAPPING:
return new String[] { "PATCH" };
}
}
return null;
}
private String[] getAttributeValues(Annotation node, SpringIndexerJavaContext context, Set<String> attributeNames) {
String[] result = getAttributeValuesFromAnnotation(node, context, attributeNames);
// extract from parent annotations
if (result == null) {
Annotation parentAnnotation = getAnnotationFromClassLevel(node);
if (parentAnnotation != null) {
result = getAttributeValuesFromAnnotation(parentAnnotation, context, attributeNames);
}
else {
result = getAttributeValuesFromSupertypes(node, context, attributeNames);
}
}
return result;
}
private String[] getAttributeValuesFromAnnotation(Annotation node, SpringIndexerJavaContext context, Set<String> attributeNames) {
if (node.isNormalAnnotation()) {
NormalAnnotation normNode = (NormalAnnotation) node;
List<?> values = normNode.values();
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
Object object = iterator.next();
if (object instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) object;
String valueName = pair.getName().getIdentifier();
if (valueName != null && attributeNames.contains(valueName)) {
Expression expression = pair.getValue();
return ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
}
}
}
}
return null;
}
private String[] getAttributeValuesFromSupertypes(Annotation node, SpringIndexerJavaContext context, Set<String> attributeNames) {
IAnnotationBinding annotationBinding = getAnnotationFromSupertypes(node, context);
IMemberValuePairBinding valuePair = getValuePair(annotationBinding, attributeNames);
ASTUtils.MemberValuePairAndType result = ASTUtils.getValuesFromValuePair(valuePair);
if (result != null) {
if (result.dereferencedType != null) {
context.addDependency(result.dereferencedType);
}
return result.values;
}
else {
return null;
}
}
private IMemberValuePairBinding getValuePair(IAnnotationBinding annotationBinding, Set<String> names) {
if (annotationBinding != null) {
IMemberValuePairBinding[] valuePairs = annotationBinding.getDeclaredMemberValuePairs();
if (valuePairs != null ) {
for (int j = 0; j < valuePairs.length; j++) {
String valueName = valuePairs[j].getName();
if (valueName != null && names.contains(valueName)) {
return valuePairs[j];
}
}
}
}
return null;
}
private Annotation getAnnotationFromClassLevel(Annotation node) {
// lookup class level request mapping annotation
ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null;
while (parent != null && !(parent instanceof TypeDeclaration)) {
parent = parent.getParent();
}
if (parent != null) {
TypeDeclaration type = (TypeDeclaration) parent;
List<?> modifiers = type.modifiers();
Iterator<?> iterator = modifiers.iterator();
while (iterator.hasNext()) {
Object modifier = iterator.next();
if (modifier instanceof Annotation) {
Annotation annotation = (Annotation) modifier;
ITypeBinding resolvedType = annotation.resolveTypeBinding();
String annotationType = resolvedType.getQualifiedName();
if (annotationType != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotationType)) {
return annotation;
}
}
}
}
return null;
}
private IAnnotationBinding getAnnotationFromSupertypes(Annotation node, SpringIndexerJavaContext context) {
ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null;
while (parent != null && !(parent instanceof TypeDeclaration)) {
parent = parent.getParent();
}
if (parent != null) {
TypeDeclaration type = (TypeDeclaration) parent;
ITypeBinding typeBinding = type.resolveBinding();
if (typeBinding != null) {
return findFirstRequestMappingAnnotation(typeBinding);
}
}
return null;
}
private IAnnotationBinding findFirstRequestMappingAnnotation(ITypeBinding start) {
if (start == null) {
return null;
}
IAnnotationBinding found = getRequestMappingAnnotation(start);
if (found != null) {
return found;
}
else {
// search interfaces first
ITypeBinding[] interfaces = start.getInterfaces();
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
found = findFirstRequestMappingAnnotation(interfaces[i]);
if (found != null) {
return found;
}
}
}
// search superclass second
ITypeBinding superclass = start.getSuperclass();
if (superclass != null) {
return findFirstRequestMappingAnnotation(superclass);
}
// nothing found
return null;
}
}
private IAnnotationBinding getRequestMappingAnnotation(ITypeBinding typeBinding) {
IAnnotationBinding[] annotations = typeBinding.getAnnotations();
for (int i = 0; i < annotations.length; i++) {
if (annotations[i].getAnnotationType() != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotations[i].getAnnotationType().getQualifiedName())) {
return annotations[i];
}
}
return null;
}
}

View File

@@ -35,9 +35,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.SpringIndexerJava.SCAN_PASS;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -59,36 +58,34 @@ public class WebfluxRouterSymbolProvider {
return false;
}
public static SpringIndexElement[] createWebfluxElements(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc, List<SpringIndexElement> childElements) {
public static void createWebfluxElements(Bean beanDefinition, MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
Block methodBody = methodDeclaration.getBody();
if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) {
addSymbolsForRouterFunction(methodBody, context, doc, childElements);
}
return new SpringIndexElement[0];
}
public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
Type returnType = methodDeclaration.getReturnType2();
if (returnType != null) {
ITypeBinding resolvedBinding = returnType.resolveBinding();
if (resolvedBinding != null && WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())) {
Block methodBody = methodDeclaration.getBody();
if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) {
addSymbolsForRouterFunction(methodBody, context, doc, new ArrayList<>());
}
else if (SCAN_PASS.ONE.equals(context.getPass())) {
context.getNextPassFiles().add(context.getFile());
}
}
addSymbolsForRouterFunction(beanDefinition, methodBody, context, doc);
}
}
private static void addSymbolsForRouterFunction(Block methodBody, SpringIndexerJavaContext context, TextDocument doc, List<SpringIndexElement> indexElementsCollector) {
// public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
// Type returnType = methodDeclaration.getReturnType2();
// if (returnType != null) {
//
// ITypeBinding resolvedBinding = returnType.resolveBinding();
//
// if (resolvedBinding != null && WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())) {
//
// Block methodBody = methodDeclaration.getBody();
// if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) {
// addSymbolsForRouterFunction(methodBody, context, doc, new ArrayList<>());
// }
// else if (SCAN_PASS.ONE.equals(context.getPass())) {
// context.getNextPassFiles().add(context.getFile());
// }
//
// }
// }
// }
//
private static void addSymbolsForRouterFunction(Bean beanDefinition, Block methodBody, SpringIndexerJavaContext context, TextDocument doc) {
methodBody.accept(new ASTVisitor() {
@Override
@@ -96,7 +93,7 @@ public class WebfluxRouterSymbolProvider {
IMethodBinding methodBinding = node.resolveMethodBinding();
if (methodBinding != null && WebfluxUtils.isRouteMethodInvocation(methodBinding)) {
extractMappingSymbol(node, doc, context, indexElementsCollector);
extractMappingSymbol(beanDefinition, node, doc, context);
}
return super.visit(node);
@@ -105,7 +102,7 @@ public class WebfluxRouterSymbolProvider {
});
}
protected static void extractMappingSymbol(MethodInvocation node, TextDocument doc, SpringIndexerJavaContext context, List<SpringIndexElement> indexElementsCollector) {
protected static void extractMappingSymbol(Bean beanDefinition, MethodInvocation node, TextDocument doc, SpringIndexerJavaContext context) {
WebfluxRouteElement[] pathElements = extractPath(node, doc);
WebfluxRouteElement[] httpMethods = extractMethods(node, doc);
WebfluxRouteElement[] contentTypes = extractContentTypes(node, doc);
@@ -134,8 +131,8 @@ public class WebfluxRouterSymbolProvider {
WebfluxHandlerMethodIndexElement handler = extractHandlerInformation(node, path, httpMethods, contentTypes, acceptTypes, location.getRange(), symbol.getName());
WebfluxRouteElementRangesIndexElement elements = extractElementsInformation(pathElements, httpMethods, contentTypes, acceptTypes);
if (handler != null) indexElementsCollector.add(handler);
if (elements != null) indexElementsCollector.add(elements);
if (handler != null) beanDefinition.addChild(handler);
if (elements != null) beanDefinition.addChild(elements);
} catch (BadLocationException e) {
log.error("bad location while extracting mapping symbol for " + doc.getUri(), e);

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* Copyright (c) 2018, 2025 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 org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.WorkspaceSymbol;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
@@ -22,7 +23,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
*/
public class DefaultSymbolProvider {
public static WorkspaceSymbol provideDefaultSymbol(Annotation node, TextDocument doc) throws Exception {
public static WorkspaceSymbol provideDefaultSymbol(Annotation node, TextDocument doc) throws BadLocationException {
WorkspaceSymbol symbol = new WorkspaceSymbol(node.toString(), SymbolKind.Interface,
Either.forLeft(new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()))));
return symbol;

View File

@@ -22,29 +22,37 @@ import org.eclipse.lsp4j.WorkspaceSymbol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.beans.CachedBean;
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class RestrictedDefaultSymbolProvider extends AbstractSymbolProvider {
public class RestrictedDefaultSymbolProvider implements SymbolProvider {
private static final Logger log = LoggerFactory.getLogger(RestrictedDefaultSymbolProvider.class);
@Override
protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding,
Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
// provide default symbol only in case this annotation is not combined with @Bean annotation
if (!isCombinedWithAnnotation(node, Annotations.BEAN)) {
try {
WorkspaceSymbol symbol = DefaultSymbolProvider.provideDefaultSymbol(node, doc);
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
context.getBeans().add(new CachedBean(context.getDocURI(), new SimpleSymbolElement(symbol)));
} catch (Exception e) {
// SimpleSymbolElement symbolIndexElement = new SimpleSymbolElement(symbol);
// SpringIndexElement parentIndexElement = context.getNearestIndexElementForNode(node.getParent());
// if (parentIndexElement != null) {
// parentIndexElement.addChild(symbolIndexElement);
// }
// else {
// context.getBeans().add(new CachedBean(context.getDocURI(), symbolIndexElement));
// }
// context.setIndexElementForASTNode(node.getParent(), symbolIndexElement);
} catch (BadLocationException e) {
log.warn(e.getMessage());
}
}

View File

@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.index.cache.IndexCache;
import org.springframework.ide.vscode.boot.index.cache.IndexCacheKey;
import org.springframework.ide.vscode.boot.java.beans.BeanUtils;
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider;
import org.springframework.ide.vscode.boot.java.beans.BeansIndexer;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
@@ -142,7 +142,7 @@ public class SpringFactoriesIndexer implements SpringIndexer {
String beanId = BeanUtils.getBeanNameFromType(simpleName);
Range range = doc.toRange(new Region(pair.getOffset(), pair.getLength()));
symbols.add(new WorkspaceSymbol(
BeansSymbolProvider.beanLabel(false, beanId, fqName, Paths.get(URI.create(docURI)).getFileName().toString()),
BeansIndexer.beanLabel(false, beanId, fqName, Paths.get(URI.create(docURI)).getFileName().toString()),
SymbolKind.Interface,
Either.forLeft(new Location(docURI, range))));
} catch (Exception e) {

View File

@@ -73,7 +73,6 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement;
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
import org.springframework.ide.vscode.commons.util.UriUtil;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -87,10 +86,6 @@ import com.google.gson.JsonObject;
*/
public class SpringIndexerJava implements SpringIndexer {
public static enum SCAN_PASS {
ONE, TWO
}
private static final Logger log = LoggerFactory.getLogger(SpringIndexerJava.class);
// whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid,
@@ -308,7 +303,7 @@ public class SpringIndexerJava implements SpringIndexer {
IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
lastModified, docRef, content, generatedSymbols, generatedBeans, problemCollector, SCAN_PASS.ONE, new ArrayList<>(), !ignoreMethodBodies);
lastModified, docRef, content, generatedSymbols, generatedBeans, problemCollector, new ArrayList<>(), !ignoreMethodBodies);
scanAST(context, true);
@@ -359,7 +354,7 @@ public class SpringIndexerJava implements SpringIndexer {
AtomicReference<TextDocument> docRef = new AtomicReference<>();
String file = UriUtil.toFileString(docURI);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, SCAN_PASS.ONE, new ArrayList<>(), true);
0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, new ArrayList<>(), true);
scanAST(context, false);
@@ -396,7 +391,7 @@ public class SpringIndexerJava implements SpringIndexer {
AtomicReference<TextDocument> docRef = new AtomicReference<>();
String file = UriUtil.toFileString(docURI);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, SCAN_PASS.ONE, new ArrayList<>(), true);
0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, new ArrayList<>(), true);
scanAST(context, false);
@@ -457,7 +452,7 @@ public class SpringIndexerJava implements SpringIndexer {
IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath,
lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, SCAN_PASS.ONE, new ArrayList<>(), !ignoreMethodBodies);
lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, new ArrayList<>(), !ignoreMethodBodies);
scanAST(context, true);
@@ -560,11 +555,11 @@ public class SpringIndexerJava implements SpringIndexer {
for (int i = 0; i < chunks.size(); i++) {
log.info("scan java files, AST parse, chunk {} for files: {}", i, javaFiles.length);
String[] pass2Files = scanFiles(project, annotations, chunks.get(i), generatedSymbols, generatedBeans, diagnosticsAggregator, SCAN_PASS.ONE);
String[] pass2Files = scanFiles(project, annotations, chunks.get(i), generatedSymbols, generatedBeans, diagnosticsAggregator, true);
if (pass2Files.length > 0) {
log.info("scan java files, AST parse, pass 2, chunk {} for files: {}", i, javaFiles.length);
scanFiles(project, annotations, pass2Files, generatedSymbols, generatedBeans, diagnosticsAggregator, SCAN_PASS.TWO);
scanFiles(project, annotations, pass2Files, generatedSymbols, generatedBeans, diagnosticsAggregator, false);
}
}
@@ -602,13 +597,12 @@ public class SpringIndexerJava implements SpringIndexer {
}
private String[] scanFiles(IJavaProject project, AnnotationHierarchies annotations, String[] javaFiles, List<CachedSymbol> generatedSymbols, List<CachedBean> generatedBeans,
BiConsumer<String, Diagnostic> diagnosticsAggregator, SCAN_PASS pass) throws Exception {
BiConsumer<String, Diagnostic> diagnosticsAggregator, boolean ignoreMethodBodies) throws Exception {
PercentageProgressTask progressTask = this.progressService.createPercentageProgressTask(INDEX_FILES_TASK_ID + project.getElementName(),
javaFiles.length, "Spring Tools: Indexing Java Sources for '" + project.getElementName() + "'");
List<String> nextPassFiles = new ArrayList<>();
final boolean ignoreMethodBodies = SCAN_PASS.ONE.equals(pass);
FileASTRequestor requestor = new FileASTRequestor() {
@@ -622,7 +616,7 @@ public class SpringIndexerJava implements SpringIndexer {
IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath,
lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, pass, nextPassFiles, !ignoreMethodBodies);
lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, nextPassFiles, !ignoreMethodBodies);
scanAST(context, true);
progressTask.increment();
@@ -655,69 +649,95 @@ public class SpringIndexerJava implements SpringIndexer {
}
private void scanAST(final SpringIndexerJavaContext context, boolean includeReconcile) {
context.getCu().accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration node) {
try {
context.addScannedType(node.resolveBinding());
extractSymbolInformation(node, context);
try {
context.getCu().accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration node) {
try {
context.addScannedType(node.resolveBinding());
extractSymbolInformation(node, context);
}
catch (RequiredCompleteAstException e) {
throw e;
}
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);
}
catch (Exception e) {
log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e);
@Override
public boolean visit(MethodDeclaration node) {
try {
extractSymbolInformation(node, context);
}
catch (RequiredCompleteAstException e) {
throw e;
}
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);
}
return super.visit(node);
@Override
public boolean visit(SingleMemberAnnotation node) {
try {
extractSymbolInformation(node, context);
}
catch (RequiredCompleteAstException e) {
throw e;
}
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(NormalAnnotation node) {
try {
extractSymbolInformation(node, context);
}
catch (RequiredCompleteAstException e) {
throw e;
}
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 (RequiredCompleteAstException e) {
throw e;
}
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);
}
});
}
catch (RequiredCompleteAstException e) {
if (!context.isFullAst()) {
context.getNextPassFiles().add(context.getFile());
context.resetDocumentRelatedElements(context.getDocURI());
}
@Override
public boolean visit(MethodDeclaration 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);
else {
log.error("Complete AST required but it is complete already. Analyzing ", context.getDocURI());
}
@Override
public boolean visit(SingleMemberAnnotation 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(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);
}
});
}
if (includeReconcile) {
reconcile(context);
}
@@ -754,12 +774,12 @@ public class SpringIndexerJava implements SpringIndexer {
reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), problemCollector, context.isFullAst());
problemCollector.endCollecting();
} catch (RequiredCompleteAstException e) {
if (context.getPass() == SCAN_PASS.TWO) {
problemCollector.endCollecting();
log.error("Complete AST required but it is complete already. Parsing ", context.getDocURI());
} else {
if (!context.isFullAst()) {
// Let problems be found in the next pass, don't add the problems to the aggregate problems collector to not duplicate them with the next pass
context.getNextPassFiles().add(context.getFile());
} else {
problemCollector.endCollecting();
log.error("Complete AST required but it is complete already. Parsing ", context.getDocURI());
}
}
}
@@ -814,7 +834,16 @@ public class SpringIndexerJava implements SpringIndexer {
WorkspaceSymbol symbol = provideDefaultSymbol(node, context);
if (symbol != null) {
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
context.getBeans().add(new CachedBean(context.getDocURI(), new SimpleSymbolElement(symbol)));
// SimpleSymbolElement symbolIndexElement = new SimpleSymbolElement(symbol);
// SpringIndexElement parentIndexElement = context.getNearestIndexElementForNode(node.getParent());
// if (parentIndexElement != null) {
// parentIndexElement.addChild(symbolIndexElement);
// }
// else {
// context.getBeans().add(new CachedBean(context.getDocURI(), symbolIndexElement));
// }
// context.setIndexElementForASTNode(node.getParent(), symbolIndexElement);
}
}
@@ -837,6 +866,9 @@ public class SpringIndexerJava implements SpringIndexer {
}
}
}
catch (RequiredCompleteAstException e) {
throw e;
}
catch (Exception e) {
log.error("error creating default symbol in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e);
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2023 Pivotal, Inc.
* Copyright (c) 2017, 2025 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,6 +11,7 @@
package org.springframework.ide.vscode.boot.java.utils;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
@@ -18,7 +19,6 @@ import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.springframework.ide.vscode.boot.java.beans.CachedBean;
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.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -38,14 +38,12 @@ public class SpringIndexerJavaContext {
private final List<CachedSymbol> generatedSymbols;
private final List<CachedBean> beans;
private final IProblemCollector getProblemCollector;
private final SCAN_PASS pass;
private final List<String> nextPassFiles;
private final boolean fullAst;
private final Set<String> dependencies = new HashSet<>();
private final Set<String> scannedTypes = new HashSet<>();
public SpringIndexerJavaContext(
IJavaProject project,
CompilationUnit cu,
@@ -57,7 +55,6 @@ public class SpringIndexerJavaContext {
List<CachedSymbol> generatedSymbols,
List<CachedBean> beans,
IProblemCollector problemCollector,
SCAN_PASS pass,
List<String> nextPassFiles,
boolean fullAst
) {
@@ -72,7 +69,6 @@ public class SpringIndexerJavaContext {
this.generatedSymbols = generatedSymbols;
this.getProblemCollector = problemCollector;
this.beans = beans;
this.pass = pass;
this.nextPassFiles = nextPassFiles;
this.fullAst = fullAst;
}
@@ -113,10 +109,6 @@ public class SpringIndexerJavaContext {
return beans;
}
public SCAN_PASS getPass() {
return pass;
}
public List<String> getNextPassFiles() {
return nextPassFiles;
}
@@ -154,4 +146,22 @@ public class SpringIndexerJavaContext {
public boolean isFullAst() {
return fullAst;
}
public void resetDocumentRelatedElements(String docURI) {
Iterator<CachedBean> beansIterator = beans.iterator();
while (beansIterator.hasNext()) {
if (beansIterator.next().getDocURI().equals(docURI)) {
beansIterator.remove();
}
}
Iterator<CachedSymbol> symbolsIterator = generatedSymbols.iterator();
while (symbolsIterator.hasNext()) {
if (symbolsIterator.next().getDocURI().equals(docURI)) {
symbolsIterator.remove();
}
}
}
}

View File

@@ -0,0 +1,148 @@
/*******************************************************************************
* Copyright (c) 2025 Broadcom
* 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.index.test;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import java.io.File;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexElement;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement;
import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement;
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* @author Martin Lippert
*/
@ExtendWith(SpringExtension.class)
@BootLanguageServerTest
@Import(SymbolProviderTestConf.class)
public class SpringIndexStructureTest {
@Autowired private BootLanguageServerHarness harness;
@Autowired private JavaProjectFinder projectFinder;
@Autowired private SpringMetamodelIndex springIndex;
@Autowired private SpringSymbolIndex indexer;
private static final String PROJECT_NAME = "test-spring-index-structure";
private File directory;
@BeforeEach
public void setup() throws Exception {
harness.intialize(null);
directory = new File(ProjectsHarness.class.getResource("/test-projects/" + PROJECT_NAME + "/").toURI());
String projectDir = directory.toURI().toString();
// trigger project creation
projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
CompletableFuture<Void> initProject = indexer.waitOperation();
initProject.get(55555555, TimeUnit.SECONDS);
}
@Test
@Disabled
void testStructureMainClass() {
String docUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
/*
* @SpringBootApplication (mainClass) - bean node
* - @Value("server.port") - default node
* - @Bean (bean1) - bean node
*/
// document node
DocumentElement document = springIndex.getDocument(docUri);
List<SpringIndexElement> docChildren = document.getChildren();
assertEquals(1, docChildren.size());
// mainClass - bean node
Bean[] mainClassBean = springIndex.getBeansWithName(PROJECT_NAME, "mainClass");
assertEquals(1, mainClassBean.length);
assertEquals("mainClass", mainClassBean[0].getName());
assertSame(mainClassBean[0], docChildren.get(0));
// mainClass children
List<SpringIndexElement> mainChildren = mainClassBean[0].getChildren();
assertEquals(2, mainChildren.size());
SpringIndexElement valueFieldNode = mainChildren.get(0);
SpringIndexElement beanMethodNode = mainChildren.get(1);
assertTrue(valueFieldNode instanceof SimpleSymbolElement);
assertTrue(beanMethodNode instanceof Bean);
SimpleSymbolElement valueField = (SimpleSymbolElement) valueFieldNode;
Bean beanMethod = (Bean) beanMethodNode;
assertEquals("bean1", beanMethod.getName());
assertEquals("@Value(\"server.port\")", valueField.getDocumentSymbol().getName());
}
@Test
void testStructureRestController() {
String docUri = directory.toPath().resolve("src/main/java/org/test/RestControllerExample.java").toUri().toString();
/*
* @RestController (mainClass) - bean node
* - /owners/find -- GET -> @GetMapping("/owners/find")
* - /owners/new -- POST -> @PostMapping("/owners/new")
*/
// document node
DocumentElement document = springIndex.getDocument(docUri);
List<SpringIndexElement> docChildren = document.getChildren();
assertEquals(1, docChildren.size());
// rest controller node
Bean[] mainClassBean = springIndex.getBeansWithName(PROJECT_NAME, "restControllerExample");
assertEquals(1, mainClassBean.length);
assertEquals("restControllerExample", mainClassBean[0].getName());
assertSame(mainClassBean[0], docChildren.get(0));
// rest controller children
List<SpringIndexElement> mainChildren = mainClassBean[0].getChildren();
assertEquals(3, mainChildren.size());
RequestMappingIndexElement getMappingNode = (RequestMappingIndexElement) mainChildren.get(0);
RequestMappingIndexElement postMappingNode = (RequestMappingIndexElement) mainChildren.get(1);
RequestMappingIndexElement genericMappingNode = (RequestMappingIndexElement) mainChildren.get(2);
assertEquals("/owners/find", getMappingNode.getPath());
assertEquals("/owners/new", postMappingNode.getPath());
assertEquals("/owners/{ownerId}/edit", genericMappingNode.getPath());
}
}

View File

@@ -190,10 +190,10 @@ public class SpringIndexerEventsTest {
Bean[] beans = springIndex.getBeansOfDocument(docUri);
assertEquals(1, beans.length);
Bean listenerComponentBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("customEventPublisher")).findFirst().get();
assertEquals("com.example.events.demo.CustomEventPublisher", listenerComponentBean.getType());
Bean publisherComponentBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("customEventPublisher")).findFirst().get();
assertEquals("com.example.events.demo.CustomEventPublisher", publisherComponentBean.getType());
List<SpringIndexElement> children = listenerComponentBean.getChildren();
List<SpringIndexElement> children = publisherComponentBean.getChildren();
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof EventPublisherIndexElement);
@@ -226,4 +226,15 @@ public class SpringIndexerEventsTest {
assertFalse(eventTypesFromHierarchy.contains("java.lang.String"));
}
@Test
void testEventPublisherWithMoreSymbols() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisherWithAdditionalElements.java").toUri().toString();
SpringIndexerHarness.assertDocumentSymbols(indexer, docUri,
SpringIndexerHarness.symbol("@Qualifier(\"qualifier\")", "@Qualifier(\"qualifier\")"),
SpringIndexerHarness.symbol("@Component", "@+ 'customEventPublisherWithAdditionalElements' (@Component) CustomEventPublisherWithAdditionalElements"),
SpringIndexerHarness.symbol("this.publisher.publishEvent(new CustomEvent())", "@EventPublisher (CustomEvent)")
);
}
}

View File

@@ -85,7 +85,7 @@ public class RequestMappingDependentConstantChangedTest {
String docUri = directory.resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString();
String constantsUri = directory.resolve("src/main/java/org/test/Constants.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertEquals(2, symbols.size());
assertSymbol(docUri, "@/path/from/constant", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
TestFileScanListener fileScanListener = new TestFileScanListener();
@@ -99,7 +99,7 @@ public class RequestMappingDependentConstantChangedTest {
fileScanListener.assertScannedUri(docUri, 1);
symbols = indexer.getSymbols(docUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(docUri, "@/changed-path", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
}
@@ -108,7 +108,7 @@ public class RequestMappingDependentConstantChangedTest {
String docUri = directory.resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString();
String constantsUri = directory.resolve("src/main/java/org/test/Constants.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertEquals(2, symbols.size());
assertSymbol(docUri, "@/path/from/constant", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
TestFileScanListener fileScanListener = new TestFileScanListener();
@@ -122,7 +122,7 @@ public class RequestMappingDependentConstantChangedTest {
fileScanListener.assertScannedUri(docUri, 1);
symbols = indexer.getSymbols(docUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(docUri, "@/changed-path", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
}
@@ -132,14 +132,14 @@ public class RequestMappingDependentConstantChangedTest {
String chainConstantsUri_2 = directory.resolve("src/main/java/org/test/ChainElement2.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertEquals(2, symbols.size());
assertSymbol(docUri, "@/path/from/chain", "@RequestMapping(ChainElement1.MAPPING_PATH_1)");
replaceInFile(chainConstantsUri_2, "path/from/chain", "/changed-path");
indexer.updateDocument(chainConstantsUri_2, null, "triggered by test code").get();
symbols = indexer.getSymbols(docUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(docUri, "@/path/from/chain", "@RequestMapping(ChainElement1.MAPPING_PATH_1)");
// You would expect here that the symbol got updated from "path/from/chain" to the changed value "/changed-path",
@@ -159,12 +159,12 @@ public class RequestMappingDependentConstantChangedTest {
{
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(pingUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
}
{
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(pongUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(pongUri, "@/ping -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
}
@@ -173,12 +173,12 @@ public class RequestMappingDependentConstantChangedTest {
{
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(pingUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
}
{
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(pongUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(pongUri, "@/changed -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
}
}
@@ -192,12 +192,12 @@ public class RequestMappingDependentConstantChangedTest {
{
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(pingUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
}
{
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(pongUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(pongUri, "@/ping -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
}
@@ -206,12 +206,12 @@ public class RequestMappingDependentConstantChangedTest {
{
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(pingUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
}
{
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(pongUri);
assertSymbolCount(1, symbols);
assertSymbolCount(2, symbols);
assertSymbol(pongUri, "@/changed -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
}
}

View File

@@ -79,11 +79,15 @@ public class RequestMappingSymbolProviderTest {
CompletableFuture<Void> initProject = indexer.waitOperation();
initProject.get(5, TimeUnit.SECONDS);
}
private List<? extends WorkspaceSymbol> getSymbols(String docUri) {
return indexer.getWorkspaceSymbolsFromSymbolIndex(docUri);
}
@Test
void testSimpleRequestMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/greeting", docUri, 8, 1, 8, 29));
}
@@ -110,9 +114,9 @@ public class RequestMappingSymbolProviderTest {
void testSimpleRequestMappingSymbolFromConstantInDifferentClass() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString();
String constantsUri = directory.toPath().resolve("src/main/java/org/test/Constants.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 6, 1, 6, 48));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 8, 1, 8, 48));
//Verify whether dependency tracker logics works properly for this example.
SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker();
@@ -135,9 +139,9 @@ public class RequestMappingSymbolProviderTest {
void testUpdateDocumentWithConstantFromDifferentClass() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString();
String constantsUri = directory.toPath().resolve("src/main/java/org/test/Constants.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 6, 1, 6, 48));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 8, 1, 8, 48));
//Verify whether dependency tracker logics works properly for this example.
SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker();
@@ -190,9 +194,9 @@ public class RequestMappingSymbolProviderTest {
@Test
void testSimpleRequestMappingSymbolFromConstantInSameClass() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInSameClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/request/mapping/path/from/same/class/constant", docUri, 8, 1, 8, 52));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/request/mapping/path/from/same/class/constant", docUri, 10, 1, 10, 52));
SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker();
assertEquals(ImmutableSet.of(), dt.getAllDependencies().get(UriUtil.toFileString(docUri)));
@@ -201,9 +205,9 @@ public class RequestMappingSymbolProviderTest {
@Test
void testSimpleRequestMappingSymbolFromConstantInBinaryType() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantFromBinaryType.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/(inferred)", docUri, 7, 1, 7, 53));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/(inferred)", docUri, 9, 1, 9, 53));
SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker();
assertEquals(ImmutableSet.of(), dt.getAllDependencies().get(UriUtil.toFileString(docUri)));
@@ -212,79 +216,79 @@ public class RequestMappingSymbolProviderTest {
@Test
void testParentRequestMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/ParentMappingClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/parent/greeting -- GET", docUri, 8, 1, 8, 51));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/parent/greeting -- GET", docUri, 11, 1, 11, 51));
}
@Test
void testEmptyPathWithParentRequestMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/ParentMappingClass2.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/parent2 -- GET,POST,DELETE", docUri, 11, 1, 11, 16));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/parent2 -- GET,POST,DELETE", docUri, 13, 1, 13, 16));
}
@Test
void testParentRequestMappingSymbolWithPathAttribute() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/ParentMappingClass3.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/parent3/greeting -- GET", docUri, 8, 1, 8, 51));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/parent3/greeting -- GET", docUri, 11, 1, 11, 51));
}
@Test
void testMappingPathFromSuperclass() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParent.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/superclasspath/", docUri, 6, 1, 6, 21));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/superclasspath/", docUri, 8, 1, 8, 21));
}
@Test
void testMappingPathFromSuperclassWithConstant() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithConstant.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/path/from/constant/", docUri, 6, 1, 6, 21));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/path/from/constant/", docUri, 8, 1, 8, 21));
}
@Test
void testMappingPathFromSuperclassWithStringConcatenation() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenation.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/superpath/subclass", docUri, 6, 1, 6, 34));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/superpath/subclass", docUri, 8, 1, 8, 34));
}
@Test
void testMappingPathFromSuperclassWithStringConcatenationPerAttribute() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenationPerAttribute.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/superpath/subclass", docUri, 6, 1, 6, 42));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/superpath/subclass", docUri, 8, 1, 8, 42));
}
@Test
void testMappingPathFromSuperclassWithMethodsAndPathAttribute() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithMethods.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/superclasspath -- POST,PUT - Accept: testconsume - Content-Type: text/plain", docUri, 6, 1, 6, 16));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/superclasspath -- POST,PUT - Accept: testconsume - Content-Type: text/plain", docUri, 8, 1, 8, 16));
}
@Test
void testMappingPathFromMultiLevelClassHierarchy() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SuperControllerLevel4.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/path-level2/final-subclass-path", docUri, 6, 1, 6, 39));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/path-level2/final-subclass-path", docUri, 8, 1, 8, 39));
}
@Test
void testMappingPathFromSuperInterfaceEvenIfSuperclassContainsMappingPath() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/ControllerAsSubclassAndInterfaceHierarchy.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/superinterface-path/last-path-segment -- GET - Accept: testconsume - Content-Type: text/plain", docUri, 8, 1, 8, 33));
}
@@ -317,101 +321,101 @@ public class RequestMappingSymbolProviderTest {
@Test
void testMultiRequestMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/MultiRequestMappingClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/hello1", docUri, 6, 1, 6, 44));
assertTrue(containsSymbol(symbols, "@/hello2", docUri, 6, 1, 6, 44));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(3, symbols.size());
assertTrue(containsSymbol(symbols, "@/hello1", docUri, 8, 1, 8, 44));
assertTrue(containsSymbol(symbols, "@/hello2", docUri, 8, 1, 8, 44));
}
@Test
void testGetMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/getData -- GET", docUri, 13, 1, 13, 24));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/getData -- GET", docUri, 16, 1, 16, 24));
}
@Test
void testGetMappingSymbolWithoutPath() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/ -- GET", docUri, 41, 1, 41, 16));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/ -- GET", docUri, 44, 1, 44, 16));
}
@Test
void testGetMappingSymbolWithoutAnything() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/ -- GET", docUri, 45, 1, 45, 14));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/ -- GET", docUri, 48, 1, 48, 14));
}
@Test
void testDeleteMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/deleteData -- DELETE", docUri, 21, 1, 21, 30));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/deleteData -- DELETE", docUri, 24, 1, 24, 30));
}
@Test
void testPostMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/postData -- POST", docUri, 25, 1, 25, 26));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/postData -- POST", docUri, 28, 1, 28, 26));
}
@Test
void testPutMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/putData -- PUT", docUri, 17, 1, 17, 24));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/putData -- PUT", docUri, 20, 1, 20, 24));
}
@Test
void testPatchMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/patchData -- PATCH", docUri, 29, 1, 29, 28));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/patchData -- PATCH", docUri, 32, 1, 32, 28));
}
@Test
void testGetRequestMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/getHello -- GET", docUri, 33, 1, 33, 61));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/getHello -- GET", docUri, 36, 1, 36, 61));
}
@Test
void testMultiRequestMethodMappingSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/postAndPutHello -- POST,PUT", docUri, 37, 1, 37, 76));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/postAndPutHello -- POST,PUT", docUri, 40, 1, 40, 76));
}
@Test
void testMediaTypes() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMappingMediaTypes.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertEquals(7, symbols.size());
assertTrue(containsSymbol(symbols, "@/consume1 -- HEAD - Accept: testconsume", docUri, 8, 1, 8, 90));
assertTrue(containsSymbol(symbols, "@/consume2 - Accept: text/plain", docUri, 13, 1, 13, 73));
assertTrue(containsSymbol(symbols, "@/consume3 - Accept: text/plain,testconsumetype", docUri, 18, 1, 18, 94));
assertTrue(containsSymbol(symbols, "@/produce1 - Content-Type: testproduce", docUri, 23, 1, 23, 60));
assertTrue(containsSymbol(symbols, "@/produce2 - Content-Type: text/plain", docUri, 28, 1, 28, 73));
assertTrue(containsSymbol(symbols, "@/produce3 - Content-Type: text/plain,testproducetype", docUri, 33, 1, 33, 94));
assertTrue(containsSymbol(symbols, "@/everything - Accept: application/json,text/plain,testconsume - Content-Type: application/json", docUri, 38, 1, 38, 170));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertEquals(8, symbols.size());
assertTrue(containsSymbol(symbols, "@/consume1 -- HEAD - Accept: testconsume", docUri, 10, 1, 10, 90));
assertTrue(containsSymbol(symbols, "@/consume2 - Accept: text/plain", docUri, 15, 1, 15, 73));
assertTrue(containsSymbol(symbols, "@/consume3 - Accept: text/plain,testconsumetype", docUri, 20, 1, 20, 94));
assertTrue(containsSymbol(symbols, "@/produce1 - Content-Type: testproduce", docUri, 25, 1, 25, 60));
assertTrue(containsSymbol(symbols, "@/produce2 - Content-Type: text/plain", docUri, 30, 1, 30, 73));
assertTrue(containsSymbol(symbols, "@/produce3 - Content-Type: text/plain,testproducetype", docUri, 35, 1, 35, 94));
assertTrue(containsSymbol(symbols, "@/everything - Accept: application/json,text/plain,testconsume - Content-Type: application/json", docUri, 40, 1, 40, 170));
}
@Test
void testPathWithConcatenatedString() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/MappingsWithConcatenatedStrings.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/path1/path2 -- GET", docUri, 13, 1, 13, 33));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/path1/path2 -- GET", docUri, 16, 1, 16, 33));
}
@Test
void testPathWithConcatenatedStringAndConstantInvolved() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/MappingsWithConcatenatedStrings.java").toUri().toString();
List<? extends WorkspaceSymbol> symbols = indexer.getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/path1/path/from/constant -- GET", docUri, 17, 1, 17, 56));
List<? extends WorkspaceSymbol> symbols = getSymbols(docUri);
assertTrue(containsSymbol(symbols, "@/path1/path/from/constant -- GET", docUri, 20, 1, 20, 56));
}
private boolean containsSymbol(List<? extends WorkspaceSymbol> symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) {

View File

@@ -0,0 +1,21 @@
package com.example.events.demo;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Qualifier("qualifier")
@Component
public class CustomEventPublisherWithAdditionalElements {
private ApplicationEventPublisher publisher;
public CustomEventPublisherWithAdditionalElements(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void foo() {
this.publisher.publishEvent(new CustomEvent());
}
}

View File

@@ -1,7 +1,9 @@
package org.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ChainedRequestMappingPathOverMultipleClasses {
@RequestMapping(ChainElement1.MAPPING_PATH_1)

View File

@@ -9,6 +9,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
import org.springframework.stereotype.Controller;
@Controller
public class MappingsWithConcatenatedStrings {
@GetMapping("/path1" + "/path2")

View File

@@ -1,7 +1,9 @@
package org.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MultiRequestMappingClass {
@RequestMapping(path= {"/hello1","hello2"})

View File

@@ -3,6 +3,9 @@ package org.test;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.*;
import org.springframework.stereotype.Controller;
@Controller
@RequestMapping(value = "parent", method = {GET,POST,DELETE})
public class ParentMappingClass {

View File

@@ -4,8 +4,10 @@ import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping(value = "parent2", method = {GET,POST,DELETE})
public class ParentMappingClass2 {

View File

@@ -3,6 +3,9 @@ package org.test;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.*;
import org.springframework.stereotype.Controller;
@Controller
@RequestMapping(path = "parent3", method = {GET,POST,DELETE})
public class ParentMappingClass3 {

View File

@@ -1,7 +1,9 @@
package org.test;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PingConstantRequestMapping {
public final static String PING = "/ping";

View File

@@ -1,7 +1,9 @@
package org.test;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PongConstantRequestMapping {
public static final String PONG="/pong";

View File

@@ -1,9 +1,11 @@
package org.test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class RequestMappingMediaTypes {
@RequestMapping(path="/consume1", consumes = "testconsume", method= {RequestMethod.HEAD})

View File

@@ -9,6 +9,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
import org.springframework.stereotype.Controller;
@Controller
public class RequestMethodClass {
@GetMapping("/getData")

View File

@@ -2,7 +2,9 @@ package org.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.stereotype.Controller;
@Controller
public class SimpleMappingClassWithConstantFromBinaryType {
@RequestMapping(AbstractBeanDefinition.INFER_METHOD)

View File

@@ -1,7 +1,9 @@
package org.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SimpleMappingClassWithConstantInDifferentClass {
@RequestMapping(Constants.REQUEST_MAPPING_PATH)

View File

@@ -1,7 +1,9 @@
package org.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SimpleMappingClassWithConstantInSameClass {
private static final String REQUEST_MAPPING_PATH_IN_SAME_CLASS = "/request/mapping/path/from/same/class/constant";

View File

@@ -1,7 +1,9 @@
package org.test.inheritance;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SubclassWithMappingFromParent extends SuperclassWithMappingPath {
@RequestMapping("/")

View File

@@ -1,7 +1,9 @@
package org.test.inheritance;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SubclassWithMappingFromParentWithConstant extends SuperclassWithMappingPathFromConstant {
@RequestMapping("/")

View File

@@ -1,7 +1,9 @@
package org.test.inheritance;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SubclassWithMappingFromParentWithMethods extends SuperclassWithMappingPathAndMethods {
@RequestMapping

View File

@@ -1,7 +1,9 @@
package org.test.inheritance;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SubclassWithMappingFromParentWithStringConcatenation extends SuperclassWithMappingPathWithStringConcatenation {
@RequestMapping("/sub" + "class")

View File

@@ -1,7 +1,9 @@
package org.test.inheritance;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SubclassWithMappingFromParentWithStringConcatenationPerAttribute extends SuperclassWithMappingPathWithStringConcatenationPerAttribute {
@RequestMapping(value = "/sub" + "class")

View File

@@ -1,7 +1,9 @@
package org.test.inheritance;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SuperControllerLevel4 extends SuperControllerLevel3 {
@RequestMapping("final-subclass-path")

View File

@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

View File

@@ -0,0 +1,259 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

View File

@@ -0,0 +1,149 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>test-spring-index-structure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<jakarta-servlet.version>5.0.0</jakarta-servlet.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!--
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<source>8</source>
<detectJavaApiLink>false</detectJavaApiLink>
</configuration>
</plugin>
</plugins>
</build>
-->
</project>

View File

@@ -0,0 +1,23 @@
package org.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class MainClass {
@Value("server.port")
private String serverport;
public static void main(String[] args) throws Exception {
SpringApplication.run(MainClass.class, args);
}
@Bean
BeanClass1 bean1() {
return new BeanClass1();
}
}

View File

@@ -0,0 +1,54 @@
package org.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import jakarta.validation.Valid;
@RestController
public class RestControllerExample {
@Autowired(required = false)
private String serverPort;
@InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
}
@ModelAttribute("owner")
public String findOwner(@PathVariable(required = false) Integer ownerId) {
return "example";
}
@GetMapping("/owners/find")
public String initFindForm() {
return "owners/findOwners";
}
@PostMapping("/owners/new")
public String processCreationForm(String owner, BindingResult result, RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
redirectAttributes.addFlashAttribute("error", "There was an error in creating the owner.");
return "something";
}
return "redirect:/owners/";
}
@RequestMapping(path = "/owners/{ownerId}/edit", method = RequestMethod.POST)
public String processUpdateOwnerForm(@Valid String owner, BindingResult result, @PathVariable("ownerId") int ownerId) {
return "redirect:/owners/{ownerId}";
}
}