1+ /*******************************************************************************
2+ * Copyright (c) 2025 Broadcom, Inc.
3+ * All rights reserved. This program and the accompanying materials
4+ * are made available under the terms of the Eclipse Public License v1.0
5+ * which accompanies this distribution, and is available at
6+ * https://www.eclipse.org/legal/epl-v10.html
7+ *
8+ * Contributors:
9+ * Broadcom, Inc. - initial API and implementation
10+ *******************************************************************************/
11+ package org .springframework .ide .vscode .boot .java .data ;
12+
13+ import java .io .File ;
14+ import java .io .IOException ;
15+ import java .net .URI ;
16+ import java .nio .file .FileVisitResult ;
17+ import java .nio .file .Files ;
18+ import java .nio .file .Path ;
19+ import java .nio .file .Paths ;
20+ import java .nio .file .SimpleFileVisitor ;
21+ import java .nio .file .attribute .BasicFileAttributes ;
22+ import java .util .ArrayList ;
23+ import java .util .Arrays ;
24+ import java .util .List ;
25+ import java .util .Set ;
26+ import java .util .concurrent .atomic .AtomicReference ;
27+ import java .util .stream .Collectors ;
28+
29+ import org .eclipse .jdt .core .dom .ASTNode ;
30+ import org .eclipse .jdt .core .dom .ASTVisitor ;
31+ import org .eclipse .jdt .core .dom .CompilationUnit ;
32+ import org .eclipse .jdt .core .dom .IMethodBinding ;
33+ import org .eclipse .jdt .core .dom .MethodDeclaration ;
34+ import org .eclipse .jdt .core .dom .SimpleName ;
35+ import org .eclipse .lsp4j .LocationLink ;
36+ import org .eclipse .lsp4j .Position ;
37+ import org .eclipse .lsp4j .Range ;
38+ import org .eclipse .lsp4j .TextDocumentIdentifier ;
39+ import org .eclipse .lsp4j .jsonrpc .CancelChecker ;
40+ import org .slf4j .Logger ;
41+ import org .slf4j .LoggerFactory ;
42+ import org .springframework .ide .vscode .boot .java .IJavaDefinitionProvider ;
43+ import org .springframework .ide .vscode .boot .java .utils .ASTUtils ;
44+ import org .springframework .ide .vscode .boot .java .utils .CompilationUnitCache ;
45+ import org .springframework .ide .vscode .commons .Version ;
46+ import org .springframework .ide .vscode .commons .java .IClasspathUtil ;
47+ import org .springframework .ide .vscode .commons .java .IJavaProject ;
48+ import org .springframework .ide .vscode .commons .java .SpringProjectUtil ;
49+ import org .springframework .ide .vscode .commons .languageserver .util .SimpleTextDocumentService ;
50+ import org .springframework .ide .vscode .commons .util .BadLocationException ;
51+
52+ public class GenAotQueryMethodDefinitionProvider implements IJavaDefinitionProvider {
53+
54+ private static Logger log = LoggerFactory .getLogger (GenAotQueryMethodDefinitionProvider .class );
55+
56+ private final CompilationUnitCache cuCache ;
57+ private final SimpleTextDocumentService docService ;
58+
59+ public GenAotQueryMethodDefinitionProvider (CompilationUnitCache cuCache , SimpleTextDocumentService docService ) {
60+ this .cuCache = cuCache ;
61+ this .docService = docService ;
62+ }
63+
64+ @ Override
65+ public List <LocationLink > getDefinitions (CancelChecker cancelToken , IJavaProject project ,
66+ TextDocumentIdentifier docId , CompilationUnit cu , ASTNode n , int offset ) {
67+ if (n instanceof SimpleName && n .getParent () instanceof MethodDeclaration md ) {
68+ Version version = SpringProjectUtil .getDependencyVersion (project , "spring-data-jpa" );
69+ if (version != null && version .getMajor () >= 4 ) {
70+ IMethodBinding methodBinding = md .resolveBinding ();
71+ if (methodBinding != null && methodBinding .getDeclaringClass () != null
72+ && methodBinding .getDeclaringClass ().isInterface ()
73+ && methodBinding .getDeclaringClass () != null
74+ && ASTUtils .isAnyTypeInHierarchy (methodBinding .getDeclaringClass (),
75+ List .of (Constants .REPOSITORY_TYPE ))) {
76+ String genRepoFqn = methodBinding .getDeclaringClass ().getQualifiedName () + "Impl__Aot" ;
77+ Path relativeGenSourcePath = Paths .get ("%s.java" .formatted (genRepoFqn .replace ('.' , '/' )));
78+ List <LocationLink > defs = findInSourceFolder (project , relativeGenSourcePath , docId , md , methodBinding , genRepoFqn );
79+ return defs .isEmpty () ? findInBuildFolder (project , relativeGenSourcePath , docId , md , methodBinding , genRepoFqn ) : defs ;
80+ }
81+ }
82+ }
83+ return List .of ();
84+ }
85+
86+ private List <LocationLink > getLocationInGenFile (IJavaProject project , TextDocumentIdentifier docId , MethodDeclaration md , IMethodBinding methodBinding , Path genRepoSourcePath , String genRepoFqn ) {
87+ if (Files .exists (genRepoSourcePath )) {
88+ URI genUri = genRepoSourcePath .toUri ();
89+ return cuCache .withCompilationUnit (project , genUri , genCu -> {
90+ List <LocationLink > defs = new ArrayList <>(1 );
91+ genCu .accept (new ASTVisitor () {
92+
93+ @ Override
94+ public boolean visit (MethodDeclaration node ) {
95+ IMethodBinding genBinding = node .resolveBinding ();
96+ if (genBinding != null
97+ && genBinding .getName ().equals (methodBinding .getName ())
98+ && Arrays .equals (Arrays .stream (genBinding .getParameterTypes ()).map (b -> b .getQualifiedName ()).toArray (), Arrays .stream (methodBinding .getParameterTypes ()).map (b -> b .getQualifiedName ()).toArray () )
99+ && genRepoFqn .equals (genBinding .getDeclaringClass ().getQualifiedName ())) {
100+ LocationLink ll = new LocationLink ();
101+ ll .setTargetUri (genUri .toASCIIString ());
102+ try {
103+ ll .setOriginSelectionRange (docService .getLatestSnapshot (docId .getUri ()).toRange (md .getName ().getStartPosition (), md .getName ().getLength ()));
104+ } catch (BadLocationException e ) {
105+ log .error ("" , e );
106+ }
107+ SimpleName genName = node .getName ();
108+ int startLine = genCu .getLineNumber (genName .getStartPosition ());
109+ Position targetStartPosition = new Position (startLine , genName .getStartPosition () - genCu .getPosition (startLine , 0 ));
110+ int endLine = genCu .getLineNumber (genName .getStartPosition () + genName .getLength ());
111+ Position targetEndPosition = new Position (endLine , genName .getStartPosition () + genName .getLength () - genCu .getPosition (endLine , 0 ));
112+ Range targetRange = new Range (targetStartPosition , targetEndPosition );
113+ ll .setTargetRange (targetRange );
114+ ll .setTargetSelectionRange (targetRange );
115+ defs .add (ll );
116+ }
117+ return false ;
118+ }
119+
120+ });
121+ return defs ;
122+ });
123+ }
124+ return List .of ();
125+ }
126+
127+ private List <LocationLink > findInSourceFolder (IJavaProject project , Path relativeGenSourcePath , TextDocumentIdentifier docId , MethodDeclaration md , IMethodBinding methodBinding , String genRepoFqn ) {
128+ for (File f : IClasspathUtil .getSourceFolders (project .getClasspath ()).collect (Collectors .toSet ())) {
129+ Path genRepoSourcePath = f .toPath ().resolve (relativeGenSourcePath );
130+ return getLocationInGenFile (project , docId , md , methodBinding , genRepoSourcePath , genRepoFqn );
131+ }
132+ return List .of ();
133+ }
134+
135+ private List <LocationLink > findInBuildFolder (IJavaProject project , Path relativeGenSourcePath , TextDocumentIdentifier docId , MethodDeclaration md , IMethodBinding methodBinding , String genRepoFqn ) {
136+ Path buildDirRelativePath = null ;
137+ Path projectPath = Paths .get (project .getLocationUri ());
138+ Set <Path > outputFolders = IClasspathUtil .getOutputFolders (project .getClasspath ()).map (f -> f .toPath ()).collect (Collectors .toSet ());
139+ for (Path f : outputFolders ) {
140+ Path p = projectPath .relativize (f );
141+ if (buildDirRelativePath == null ) {
142+ buildDirRelativePath = p ;
143+ } else {
144+ int i = 0 ;
145+ for (; i < buildDirRelativePath .getNameCount () && i < p .getNameCount () && buildDirRelativePath .getName (i ).equals (p .getName (i )); i ++) {
146+ // nothing;
147+ }
148+ if (i == 0 ) {
149+ buildDirRelativePath = Paths .get ("" );
150+ break ;
151+ } else {
152+ buildDirRelativePath = buildDirRelativePath .subpath (0 , i );
153+ }
154+ }
155+ }
156+ AtomicReference <Path > genSourceFilePathRef = new AtomicReference <>();
157+ try {
158+ Files .walkFileTree (projectPath .resolve (buildDirRelativePath ), new SimpleFileVisitor <Path >() {
159+
160+ @ Override
161+ public FileVisitResult preVisitDirectory (Path dir , BasicFileAttributes attrs ) throws IOException {
162+ if (genSourceFilePathRef .get () == null && !outputFolders .stream ().anyMatch (dir ::startsWith )) {
163+ Path genPath = dir .resolve (relativeGenSourcePath );
164+ if (Files .exists (genPath )) {
165+ genSourceFilePathRef .set (genPath );
166+ } else {
167+ return FileVisitResult .CONTINUE ;
168+ }
169+ }
170+ return FileVisitResult .SKIP_SUBTREE ;
171+ }
172+
173+ @ Override
174+ public FileVisitResult visitFile (Path file , BasicFileAttributes attrs ) throws IOException {
175+ if (file .getFileName ().toString ().endsWith (".class" )) {
176+ return FileVisitResult .SKIP_SIBLINGS ;
177+ }
178+ return super .visitFile (file , attrs );
179+ }
180+
181+ });
182+ } catch (IOException e ) {
183+ log .error ("" , e );
184+ }
185+ return genSourceFilePathRef .get () == null ? List .of () : getLocationInGenFile (project , docId , md , methodBinding , genSourceFilePathRef .get (), genRepoFqn );
186+ }
187+
188+ }
0 commit comments