1010 *******************************************************************************/
1111package org .springframework .ide .vscode .boot .java .beans ;
1212
13+ import java .util .ArrayList ;
1314import java .util .Arrays ;
1415import java .util .Collection ;
1516import java .util .HashSet ;
1819import java .util .stream .Collectors ;
1920import java .util .stream .Stream ;
2021
22+ import org .eclipse .jdt .core .dom .ASTNode ;
2123import org .eclipse .jdt .core .dom .ASTVisitor ;
2224import org .eclipse .jdt .core .dom .AbstractTypeDeclaration ;
2325import org .eclipse .jdt .core .dom .Annotation ;
26+ import org .eclipse .jdt .core .dom .Block ;
2427import org .eclipse .jdt .core .dom .Expression ;
2528import org .eclipse .jdt .core .dom .IMethodBinding ;
2629import org .eclipse .jdt .core .dom .ITypeBinding ;
@@ -140,6 +143,7 @@ private void createSymbol(TypeDeclaration type, Annotation node, ITypeBinding an
140143 indexEventListenerInterfaceImplementation (beanDefinition , type , context , doc );
141144 indexRequestMappings (beanDefinition , type , annotationType , metaAnnotations , context , doc );
142145 indexConfigurationProperties (beanDefinition , type , context , doc );
146+ indexBeanRegistrarImplementation (beanDefinition , type , context , doc );
143147
144148 context .getGeneratedSymbols ().add (new CachedSymbol (context .getDocURI (), context .getLastModified (), symbol ));
145149 context .getBeans ().add (new CachedBean (context .getDocURI (), beanDefinition ));
@@ -321,6 +325,7 @@ public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext
321325 // check for event listener implementations on classes that are not annotated with component, but created via bean methods (for example)
322326 if (!isComponment ) {
323327 indexEventListenerInterfaceImplementation (null , typeDeclaration , context , doc );
328+ indexBeanRegistrarImplementation (null , typeDeclaration , context , doc );
324329 }
325330
326331 }
@@ -377,6 +382,191 @@ private MethodDeclaration findHandleEventMethod(TypeDeclaration type) {
377382 return null ;
378383 }
379384
385+ private MethodDeclaration findRegisterMethod (TypeDeclaration type , ITypeBinding beanRegistrarType ) {
386+ IMethodBinding [] beanRegistrarMethods = beanRegistrarType .getDeclaredMethods ();
387+ if (beanRegistrarMethods == null || beanRegistrarMethods .length != 1 || !"register" .equals (beanRegistrarMethods [0 ].getName ())) {
388+ return null ;
389+ }
390+
391+ MethodDeclaration [] methods = type .getMethods ();
392+
393+ for (MethodDeclaration method : methods ) {
394+ IMethodBinding binding = method .resolveBinding ();
395+ boolean overrides = binding .overrides (beanRegistrarMethods [0 ]);
396+ if (overrides ) {
397+ return method ;
398+ }
399+ }
400+
401+ return null ;
402+ }
403+
404+ private void indexBeanRegistrarImplementation (Bean bean , TypeDeclaration typeDeclaration , SpringIndexerJavaContext context , TextDocument doc ) {
405+ try {
406+ ITypeBinding typeBinding = typeDeclaration .resolveBinding ();
407+ if (typeBinding == null ) return ;
408+
409+ ITypeBinding inTypeHierarchy = ASTUtils .findInTypeHierarchy (typeDeclaration , doc , typeBinding , Set .of (Annotations .BEAN_REGISTRAR_INTERFACE ));
410+ if (inTypeHierarchy == null ) return ;
411+
412+ MethodDeclaration registerMethod = findRegisterMethod (typeDeclaration , inTypeHierarchy );
413+ if (registerMethod == null ) return ;
414+
415+ if (!context .isFullAst ()) { // needs full method bodies to continue
416+ throw new RequiredCompleteAstException ();
417+ }
418+
419+ if (bean == null ) { // need to create and register bean element
420+ String beanType = typeBinding .getQualifiedName ();
421+ String beanName = BeanUtils .getBeanNameFromType (typeBinding .getName ());
422+
423+ Location location = new Location (doc .getUri (), doc .toRange (typeDeclaration .getStartPosition (), typeDeclaration .getLength ()));
424+
425+ WorkspaceSymbol symbol = new WorkspaceSymbol (
426+ beanLabel ("+" , null , null , beanName , beanType ),
427+ SymbolKind .Class ,
428+ Either .forLeft (location ));
429+
430+ InjectionPoint [] injectionPoints = ASTUtils .findInjectionPoints (typeDeclaration , doc );
431+
432+ Set <String > supertypes = new HashSet <>();
433+ ASTUtils .findSupertypes (typeBinding , supertypes );
434+
435+ Collection <Annotation > annotationsOnMethod = ASTUtils .getAnnotations (typeDeclaration );
436+ AnnotationMetadata [] annotations = ASTUtils .getAnnotationsMetadata (annotationsOnMethod , doc );
437+
438+ bean = new Bean (beanName , beanType , location , injectionPoints , supertypes , annotations , false , symbol .getName ());
439+
440+ context .getGeneratedSymbols ().add (new CachedSymbol (context .getDocURI (), context .getLastModified (), symbol ));
441+ context .getBeans ().add (new CachedBean (context .getDocURI (), bean ));
442+ }
443+
444+ scanBeanRegistryInvocations (bean , registerMethod .getBody (), context , doc );
445+
446+ } catch (BadLocationException e ) {
447+ log .error ("" , e );
448+ }
449+ }
450+
451+ private void scanBeanRegistryInvocations (Bean component , Block body , SpringIndexerJavaContext context , TextDocument doc ) {
452+ if (body == null ) {
453+ return ;
454+ }
455+
456+ body .accept (new ASTVisitor () {
457+
458+ @ Override
459+ public boolean visit (MethodInvocation methodInvocation ) {
460+ try {
461+ String methodName = methodInvocation .getName ().toString ();
462+ if ("registerBean" .equals (methodName )) {
463+
464+ IMethodBinding methodBinding = methodInvocation .resolveMethodBinding ();
465+ ITypeBinding declaringClass = methodBinding .getDeclaringClass ();
466+
467+ if (declaringClass != null && Annotations .BEAN_REGISTRY_INTERFACE .equals (declaringClass .getQualifiedName ())) {
468+
469+ @ SuppressWarnings ("unchecked" )
470+ List <Expression > arguments = methodInvocation .arguments ();
471+ List <ITypeBinding > types = new ArrayList <>();
472+
473+ for (Expression argument : arguments ) {
474+ ITypeBinding typeBinding = argument .resolveTypeBinding ();
475+ if (typeBinding != null ) {
476+ types .add (typeBinding );
477+ }
478+ else {
479+ return true ;
480+ }
481+ }
482+
483+ if (arguments .size () == 1 && "java.lang.Class" .equals (types .get (0 ).getBinaryName ())) {
484+ // <T> String registerBean(Class<T> beanClass);
485+
486+ ITypeBinding typeBinding = types .get (0 );
487+ ITypeBinding [] typeParameters = typeBinding .getTypeArguments ();
488+ if (typeParameters != null && typeParameters .length == 1 ) {
489+ String typeParamName = typeParameters [0 ].getBinaryName ();
490+
491+ String beanName = BeanUtils .getBeanNameFromType (typeParameters [0 ].getName ());
492+ String beanType = typeParamName ;
493+
494+ createBean (component , beanName , beanType , typeParameters [0 ], methodInvocation , context , doc );
495+ }
496+ }
497+ else if (arguments .size () == 2 && "java.lang.String" .equals (types .get (0 ).getQualifiedName ()) && "java.lang.Class" .equals (types .get (1 ).getBinaryName ())) {
498+ // <T> void registerBean(String name, Class<T> beanClass);
499+
500+ String beanName = ASTUtils .getExpressionValueAsString (arguments .get (0 ), (dep ) -> {});
501+
502+ ITypeBinding typeBinding = types .get (1 );
503+ ITypeBinding [] typeParameters = typeBinding .getTypeArguments ();
504+ if (typeParameters != null && typeParameters .length == 1 ) {
505+ String typeParamName = typeParameters [0 ].getBinaryName ();
506+ String beanType = typeParamName ;
507+
508+ createBean (component , beanName , beanType , typeParameters [0 ], methodInvocation , context , doc );
509+ }
510+ }
511+ else if (arguments .size () == 2 && "java.lang.Class" .equals (types .get (0 ).getBinaryName ()) && "java.util.function.Consumer" .equals (types .get (1 ).getBinaryName ())) {
512+ // <T> String registerBean(Class<T> beanClass, Consumer<Spec<T>> customizer);
513+
514+ ITypeBinding typeBinding = types .get (0 );
515+ ITypeBinding [] typeParameters = typeBinding .getTypeArguments ();
516+ if (typeParameters != null && typeParameters .length == 1 ) {
517+ String typeParamName = typeParameters [0 ].getBinaryName ();
518+
519+ String beanName = BeanUtils .getBeanNameFromType (typeParameters [0 ].getName ());
520+ String beanType = typeParamName ;
521+
522+ createBean (component , beanName , beanType , typeParameters [0 ], methodInvocation , context , doc );
523+ }
524+ }
525+ else if (arguments .size () == 3 && "java.lang.String" .equals (types .get (0 ).getQualifiedName ())
526+ && "java.lang.Class" .equals (types .get (1 ).getBinaryName ()) && "java.util.function.Consumer" .equals (types .get (2 ).getBinaryName ())) {
527+ // <T> void registerBean(String name, Class<T> beanClass, Consumer<Spec<T>> customizer);
528+
529+ String beanName = ASTUtils .getExpressionValueAsString (arguments .get (0 ), (dep ) -> {});
530+
531+ ITypeBinding typeBinding = types .get (1 );
532+ ITypeBinding [] typeParameters = typeBinding .getTypeArguments ();
533+ if (typeParameters != null && typeParameters .length == 1 ) {
534+ String typeParamName = typeParameters [0 ].getBinaryName ();
535+ String beanType = typeParamName ;
536+
537+ createBean (component , beanName , beanType , typeParameters [0 ], methodInvocation , context , doc );
538+ }
539+ }
540+ }
541+ }
542+
543+ } catch (BadLocationException e ) {
544+ log .error ("" , e );
545+ }
546+ return super .visit (methodInvocation );
547+ }
548+ });
549+ }
550+
551+ public void createBean (Bean parentBean , String beanName , String beanType , ITypeBinding beanTypeBinding , ASTNode node , SpringIndexerJavaContext context , TextDocument doc ) throws BadLocationException {
552+ Location location = new Location (doc .getUri (), doc .toRange (node .getStartPosition (), node .getLength ()));
553+
554+ WorkspaceSymbol symbol = new WorkspaceSymbol (
555+ beanLabel ("+" , null , null , beanName , beanType ),
556+ SymbolKind .Class ,
557+ Either .forLeft (location ));
558+ context .getGeneratedSymbols ().add (new CachedSymbol (context .getDocURI (), context .getLastModified (), symbol ));
559+
560+ InjectionPoint [] injectionPoints = DefaultValues .EMPTY_INJECTION_POINTS ;
561+ Set <String > supertypes = new HashSet <>();
562+ ASTUtils .findSupertypes (beanTypeBinding , supertypes );
563+
564+ AnnotationMetadata [] annotations = DefaultValues .EMPTY_ANNOTATIONS ;
565+
566+ Bean bean = new Bean (beanName , beanType , location , injectionPoints , supertypes , annotations , false , symbol .getName ());
567+ parentBean .addChild (bean );
568+ }
569+
380570 public static String beanLabel (String searchPrefix , String annotationTypeName , Collection <String > metaAnnotationNames , String beanName , String beanType ) {
381571 StringBuilder symbolLabel = new StringBuilder ();
382572 symbolLabel .append ("@" );
@@ -385,21 +575,25 @@ public static String beanLabel(String searchPrefix, String annotationTypeName, C
385575 symbolLabel .append ('\'' );
386576 symbolLabel .append (beanName );
387577 symbolLabel .append ('\'' );
388- symbolLabel .append (" (@" );
389- symbolLabel .append (annotationTypeName );
390- if (!metaAnnotationNames .isEmpty ()) {
391- symbolLabel .append (" <: " );
392- boolean first = true ;
393- for (String ma : metaAnnotationNames ) {
394- if (!first ) {
395- symbolLabel .append (", " );
578+
579+ if (annotationTypeName != null ) {
580+ symbolLabel .append (" (@" );
581+ symbolLabel .append (annotationTypeName );
582+ if (!metaAnnotationNames .isEmpty ()) {
583+ symbolLabel .append (" <: " );
584+ boolean first = true ;
585+ for (String ma : metaAnnotationNames ) {
586+ if (!first ) {
587+ symbolLabel .append (", " );
588+ }
589+ symbolLabel .append ("@" );
590+ symbolLabel .append (ma );
591+ first = false ;
396592 }
397- symbolLabel .append ("@" );
398- symbolLabel .append (ma );
399- first = false ;
400593 }
594+ symbolLabel .append (")" );
401595 }
402- symbolLabel .append (") " );
596+ symbolLabel .append (" " );
403597 symbolLabel .append (beanType );
404598 return symbolLabel .toString ();
405599 }
0 commit comments