1616package io .github .jeddict .ai .classpath ;
1717
1818import java .util .ArrayList ;
19+ import java .util .Collections ;
1920import java .util .List ;
21+ import java .util .Set ;
2022import java .util .function .Consumer ;
23+ import java .util .stream .Collectors ;
24+ import java .util .stream .Stream ;
2125import javax .swing .text .BadLocationException ;
2226import javax .swing .text .Document ;
2327import org .netbeans .api .java .classpath .ClassPath ;
2428import org .netbeans .api .project .Project ;
2529import org .netbeans .api .project .ProjectUtils ;
2630import org .netbeans .api .project .ui .OpenProjects ;
27- import org .netbeans .modules .editor .NbEditorUtilities ;
2831import org .netbeans .spi .editor .completion .CompletionResultSet ;
2932import org .netbeans .spi .editor .completion .support .AsyncCompletionQuery ;
3033import org .openide .filesystems .FileObject ;
@@ -35,6 +38,7 @@ public class JeddictQueryCompletionQuery extends AsyncCompletionQuery {
3538 private static List <String > cachedClassNames = null ;
3639 private static long lastScanTimestamp = 0 ;
3740 private static final long CACHE_EXPIRY_MS = 60 * 1000 ;
41+ private static Set <String > cachedProjectPaths = Collections .emptySet ();
3842 public static final String JEDDICT_EDITOR_CALLBACK = "jeddict-editor-callback" ;
3943
4044 @ Override
@@ -49,34 +53,22 @@ protected void query(CompletionResultSet resultSet, Document doc, int caretOffse
4953 resultSet .finish ();
5054 return ;
5155 }
52- if (System .currentTimeMillis () - lastScanTimestamp > CACHE_EXPIRY_MS ) {
53- cachedClassNames = null ;
54- }
55- if (cachedClassNames == null ) {
56- cachedClassNames = new ArrayList <>();
57- FileObject file = NbEditorUtilities .getFileObject (doc );
58- boolean found = false ;
59- if (file != null ) {
60- ClassPath classPath = ClassPath .getClassPath (file , ClassPath .COMPILE );
61- if (classPath != null ) {
62- for (ClassPath .Entry entry : classPath .entries ()) {
63- FileObject root = entry .getRoot ();
64- if (root != null ) {
65- collectClassNames (root , "" , cachedClassNames );
66- found = true ;
67- }
68- }
69- }
70- }
71-
72- if (!found ) {
73- scanAllProjects (cachedClassNames );
56+ Project [] currentOpenProjects = OpenProjects .getDefault ().getOpenProjects ();
57+ Set <String > currentProjectPaths = openProjectPaths (currentOpenProjects );
58+ List <String > snapshot ;
59+ synchronized (JeddictQueryCompletionQuery .class ) {
60+ boolean ttlExpired = System .currentTimeMillis () - lastScanTimestamp > CACHE_EXPIRY_MS ;
61+ boolean projectsChanged = !currentProjectPaths .equals (cachedProjectPaths );
62+ if (cachedClassNames == null || ttlExpired || projectsChanged ) {
63+ cachedClassNames = new ArrayList <>();
64+ scanProjects (currentOpenProjects , cachedClassNames );
65+ cachedProjectPaths = currentProjectPaths ;
66+ lastScanTimestamp = System .currentTimeMillis ();
7467 }
75-
76- lastScanTimestamp = System .currentTimeMillis ();
68+ snapshot = cachedClassNames ;
7769 }
7870
79- for (String fqcn : cachedClassNames ) {
71+ for (String fqcn : snapshot ) {
8072 if (prefix .isEmpty () || fqcn .toLowerCase ().contains (prefix .toLowerCase ())) {
8173 resultSet .addItem (new JeddictQueryCompletionItem (fqcn , caretOffset , prefix , callback ));
8274 }
@@ -97,24 +89,41 @@ private void collectClassNames(FileObject file, String pkg, List<String> classNa
9789 }
9890 }
9991
100- private void scanAllProjects (List <String > classNames ) {
101- for (Project project : OpenProjects .getDefault ().getOpenProjects ()) {
102- for (var sourceGroup : ProjectUtils .getSources (project ).getSourceGroups ("java" )) {
103- ClassPath classPath = ClassPath .getClassPath (sourceGroup .getRootFolder (), ClassPath .SOURCE );
104- if (classPath != null ) {
105- for (ClassPath .Entry entry : classPath .entries ()) {
106- FileObject root = entry .getRoot ();
107- if (root != null ) {
108- for (FileObject child : root .getChildren ()) {
109- collectClassNames (child , "" , classNames );
110- }
111- }
112- }
92+ private boolean scanClassPathEntries (ClassPath classPath , List <String > classNames ) {
93+ boolean found = false ;
94+ for (ClassPath .Entry entry : classPath .entries ()) {
95+ FileObject root = entry .getRoot ();
96+ if (root != null ) {
97+ for (FileObject child : root .getChildren ()) {
98+ collectClassNames (child , "" , classNames );
11399 }
100+ found = true ;
101+ }
102+ }
103+ return found ;
104+ }
105+
106+ private void scanProject (Project project , List <String > classNames ) {
107+ for (var sourceGroup : ProjectUtils .getSources (project ).getSourceGroups ("java" )) {
108+ ClassPath classPath = ClassPath .getClassPath (sourceGroup .getRootFolder (), ClassPath .SOURCE );
109+ if (classPath != null ) {
110+ scanClassPathEntries (classPath , classNames );
114111 }
115112 }
116113 }
117114
115+ private void scanProjects (Project [] projects , List <String > classNames ) {
116+ for (Project project : projects ) {
117+ scanProject (project , classNames );
118+ }
119+ }
120+
121+ private static Set <String > openProjectPaths (Project [] projects ) {
122+ return Stream .of (projects )
123+ .map (p -> p .getProjectDirectory ().getPath ())
124+ .collect (Collectors .toSet ());
125+ }
126+
118127 private String extractPrefix (Document doc , int caretOffset ) {
119128 try {
120129 int start = caretOffset - 1 ;
0 commit comments