28
28
import java .util .List ;
29
29
import java .util .Map ;
30
30
import java .util .Optional ;
31
+ import java .util .Map .Entry ;
31
32
import java .util .stream .Stream ;
32
33
33
34
import org .eclipse .core .resources .IContainer ;
46
47
import org .eclipse .core .runtime .jobs .Job ;
47
48
import org .eclipse .debug .core .ILaunchConfiguration ;
48
49
import org .eclipse .jdt .core .IClasspathAttribute ;
50
+ import org .eclipse .jdt .core .IClasspathContainer ;
49
51
import org .eclipse .jdt .core .IClasspathEntry ;
50
52
import org .eclipse .jdt .core .ICompilationUnit ;
51
53
import org .eclipse .jdt .core .IJavaElement ;
54
+ import org .eclipse .jdt .core .IJavaModelStatus ;
52
55
import org .eclipse .jdt .core .IJavaProject ;
53
56
import org .eclipse .jdt .core .IParent ;
54
57
import org .eclipse .jdt .core .IType ;
@@ -147,22 +150,43 @@ public static Map<String, Object> getProjectSettings(String uri, List<String> se
147
150
settings .putIfAbsent (key , referencedLibraries );
148
151
break ;
149
152
case CLASSPATH_ENTRIES :
150
- IClasspathEntry [] entries = javaProject .getRawClasspath ();
153
+ List <IClasspathEntry > entriesToBeScan = new LinkedList <>();
154
+ Collections .addAll (entriesToBeScan , javaProject .getRawClasspath ());
151
155
List <ProjectClasspathEntry > classpathEntries = new LinkedList <>();
152
- for (IClasspathEntry entry : entries ) {
156
+ for (int i = 0 ; i < entriesToBeScan .size (); i ++) {
157
+ IClasspathEntry entry = entriesToBeScan .get (i );
153
158
IPath path = entry .getPath ();
154
159
IPath output = entry .getOutputLocation ();
155
- if (entry .getEntryKind () == IClasspathEntry .CPE_SOURCE ) {
160
+ int entryKind = entry .getEntryKind ();
161
+ if (entryKind == IClasspathEntry .CPE_SOURCE ) {
162
+ IPath relativePath = path .makeRelativeTo (project .getFullPath ());
163
+ if (relativePath .isEmpty ()) {
164
+ continue ; // A valid relative source path should not be empty.
165
+ }
156
166
path = project .getFolder (path .makeRelativeTo (project .getFullPath ())).getLocation ();
157
167
if (output != null ) {
158
168
output = project .getFolder (output .makeRelativeTo (project .getFullPath ())).getLocation ();
159
169
}
170
+ } else if (entryKind == IClasspathEntry .CPE_CONTAINER ) {
171
+ // skip JRE container, since it's already handled in VM_LOCATION.
172
+ if (!path .toString ().startsWith (JavaRuntime .JRE_CONTAINER )) {
173
+ entriesToBeScan .addAll (expandContainerEntry (javaProject , entry ));
174
+ }
175
+ continue ;
176
+ } else if (entryKind == IClasspathEntry .CPE_LIBRARY ) {
177
+ if (!path .toFile ().exists ()) {
178
+ // the case when lib path is relative
179
+ path = project .getFile (path .makeRelativeTo (project .getFullPath ())).getLocation ();
180
+ }
181
+ if (!path .toFile ().exists ()) {
182
+ continue ;
183
+ }
160
184
}
161
185
Map <String , String > attributes = new HashMap <>();
162
186
for (IClasspathAttribute attribute : entry .getExtraAttributes ()) {
163
187
attributes .put (attribute .getName (), attribute .getValue ());
164
188
}
165
- classpathEntries .add (new ProjectClasspathEntry (entry . getEntryKind () , path .toOSString (),
189
+ classpathEntries .add (new ProjectClasspathEntry (entryKind , path .toOSString (),
166
190
output == null ? null : output .toOSString (), attributes ));
167
191
}
168
192
settings .putIfAbsent (key , classpathEntries );
@@ -175,6 +199,164 @@ public static Map<String, Object> getProjectSettings(String uri, List<String> se
175
199
return settings ;
176
200
}
177
201
202
+ /**
203
+ * Update the project classpath by the given classpath entries.
204
+ */
205
+ public static void updateClasspaths (String uri , List <ProjectClasspathEntry > entries , IProgressMonitor monitor ) throws CoreException , URISyntaxException {
206
+ IJavaProject javaProject = getJavaProjectFromUri (uri );
207
+ IProject project = javaProject .getProject ();
208
+ Map <IPath , IPath > sourceAndOutput = new HashMap <>();
209
+ List <IClasspathEntry > newEntries = new LinkedList <>();
210
+ List <IClasspathEntry > newDependencyEntries = new LinkedList <>();
211
+ for (ProjectClasspathEntry entry : entries ) {
212
+ if (entry .getKind () == IClasspathEntry .CPE_SOURCE ) {
213
+ IPath path = project .getFolder (entry .getPath ()).getFullPath ();
214
+ IPath outputLocation = null ;
215
+ String output = entry .getOutput ();
216
+ if (output != null ) {
217
+ if ("." .equals (output )) {
218
+ outputLocation = project .getFullPath ();
219
+ } else {
220
+ outputLocation = project .getFolder (output ).getFullPath ();
221
+ }
222
+ }
223
+ sourceAndOutput .put (path , outputLocation );
224
+ } else if (entry .getKind () == IClasspathEntry .CPE_CONTAINER ) {
225
+ if (entry .getPath ().startsWith (JavaRuntime .JRE_CONTAINER )) {
226
+ String jdkPath = entry .getPath ().substring (JavaRuntime .JRE_CONTAINER .length ());
227
+ newEntries .add (getNewJdkEntry (javaProject , jdkPath ));
228
+ } else {
229
+ JavaLanguageServerPlugin .logInfo ("The container entry " + entry .getPath () + " is not supported to be updated." );
230
+ }
231
+ } else {
232
+ newDependencyEntries .add (convertClasspathEntry (entry ));
233
+ }
234
+ }
235
+ IClasspathEntry [] sources = ProjectUtils .resolveSourceClasspathEntries (javaProject , sourceAndOutput , Collections .emptyList (), javaProject .getOutputLocation ());
236
+ newEntries .addAll (Arrays .asList (sources ));
237
+ newEntries .addAll (resolveDependencyEntries (javaProject , newDependencyEntries ));
238
+
239
+ IClasspathEntry [] rawClasspath = newEntries .toArray (IClasspathEntry []::new );
240
+ IJavaModelStatus checkStatus = ClasspathEntry .validateClasspath (javaProject , rawClasspath , javaProject .getOutputLocation ());
241
+ if (!checkStatus .isOK ()) {
242
+ throw new CoreException (checkStatus );
243
+ }
244
+ javaProject .setRawClasspath (rawClasspath , monitor );
245
+ }
246
+
247
+ /**
248
+ * Check the new dependency entries are different from the current ones or not.
249
+ * If they are equal, return the current dependency entries, otherwise return the new ones.
250
+ */
251
+ private static List <IClasspathEntry > resolveDependencyEntries (IJavaProject javaProject , List <IClasspathEntry > newEntries ) throws JavaModelException {
252
+ List <IClasspathEntry > currentDependencyEntries = new LinkedList <>();
253
+ for (IClasspathEntry entry : javaProject .getRawClasspath ()) {
254
+ if (entry .getEntryKind () != IClasspathEntry .CPE_SOURCE &&
255
+ !entry .getPath ().toString ().startsWith (JavaRuntime .JRE_CONTAINER )) {
256
+ currentDependencyEntries .add (entry );
257
+ }
258
+ }
259
+
260
+ Map <IPath , IClasspathEntry > currentEntryMapping = new HashMap <>();
261
+ for (IClasspathEntry entry : currentDependencyEntries ) {
262
+ if (entry .getEntryKind () == IClasspathEntry .CPE_CONTAINER ) {
263
+ List <IClasspathEntry > expandedContainerEntry = expandContainerEntry (javaProject , entry );
264
+ for (IClasspathEntry containerEntry : expandedContainerEntry ) {
265
+ currentEntryMapping .put (containerEntry .getPath (), containerEntry );
266
+ }
267
+ } else {
268
+ currentEntryMapping .put (entry .getPath (), entry );
269
+ }
270
+ }
271
+
272
+ // Use new dependency entries if the size is different
273
+ if (newEntries .size () != currentEntryMapping .size ()) {
274
+ return newEntries ;
275
+ }
276
+
277
+ for (IClasspathEntry entry : newEntries ) {
278
+ IClasspathEntry currentEntry = currentEntryMapping .get (entry .getPath ());
279
+ if (currentEntry == null ) {
280
+ return newEntries ;
281
+ }
282
+ }
283
+
284
+ return currentDependencyEntries ;
285
+ }
286
+
287
+ /**
288
+ * Expand the container entry, the returned list is guaranteed to contain no container entry.
289
+ */
290
+ private static List <IClasspathEntry > expandContainerEntry (IJavaProject javaProject , IClasspathEntry entry ) throws JavaModelException {
291
+ if (entry .getEntryKind () != IClasspathEntry .CPE_CONTAINER ) {
292
+ return Collections .singletonList (entry );
293
+ }
294
+
295
+ List <IClasspathEntry > resolvedEntries = new LinkedList <>();
296
+ List <IClasspathEntry > entriesToScan = new LinkedList <>();
297
+ entriesToScan .add (entry );
298
+ for (int i = 0 ; i < entriesToScan .size (); i ++) {
299
+ IClasspathEntry currentEntry = entriesToScan .get (i );
300
+ if (currentEntry .getEntryKind () != IClasspathEntry .CPE_CONTAINER ) {
301
+ resolvedEntries .add (currentEntry );
302
+ continue ;
303
+ }
304
+ IClasspathContainer container = JavaCore .getClasspathContainer (currentEntry .getPath (), javaProject );
305
+ if (container == null ) {
306
+ continue ;
307
+ }
308
+ IClasspathEntry [] containerEntries = container .getClasspathEntries ();
309
+ for (IClasspathEntry containerEntry : containerEntries ) {
310
+ if (containerEntry .getEntryKind () == IClasspathEntry .CPE_CONTAINER ) {
311
+ entriesToScan .add (containerEntry );
312
+ } else {
313
+ resolvedEntries .add (containerEntry );
314
+ }
315
+ }
316
+ }
317
+ return resolvedEntries ;
318
+ }
319
+
320
+ /**
321
+ * Convert ProjectClasspathEntry to IClasspathEntry.
322
+ */
323
+ private static IClasspathEntry convertClasspathEntry (ProjectClasspathEntry entry ) {
324
+ List <IClasspathAttribute > attributes = new LinkedList <>();
325
+ if (entry .getAttributes () != null ) {
326
+ for (Entry <String , String > attributeEntry : entry .getAttributes ().entrySet ()) {
327
+ attributes .add (JavaCore .newClasspathAttribute (attributeEntry .getKey (), attributeEntry .getValue ()));
328
+ }
329
+ }
330
+ switch (entry .getKind ()) {
331
+ case IClasspathEntry .CPE_CONTAINER :
332
+ return JavaCore .newContainerEntry (
333
+ IPath .fromOSString (entry .getPath ()),
334
+ ClasspathEntry .NO_ACCESS_RULES ,
335
+ attributes .toArray (IClasspathAttribute []::new ),
336
+ false
337
+ );
338
+ case IClasspathEntry .CPE_LIBRARY :
339
+ return JavaCore .newLibraryEntry (
340
+ IPath .fromOSString (entry .getPath ()),
341
+ null ,
342
+ null ,
343
+ ClasspathEntry .NO_ACCESS_RULES ,
344
+ attributes .toArray (IClasspathAttribute []::new ),
345
+ false
346
+ );
347
+ case IClasspathEntry .CPE_PROJECT :
348
+ return JavaCore .newProjectEntry (
349
+ IPath .fromOSString (entry .getPath ()),
350
+ ClasspathEntry .NO_ACCESS_RULES ,
351
+ false ,
352
+ attributes .toArray (IClasspathAttribute []::new ),
353
+ false
354
+ );
355
+ default :
356
+ return null ;
357
+ }
358
+ }
359
+
178
360
/**
179
361
* Updates the project source paths.
180
362
* @param uri Uri of the project.
@@ -432,31 +614,40 @@ public static SymbolInformation resolveWorkspaceSymbol(SymbolInformation request
432
614
433
615
public static JdkUpdateResult updateProjectJdk (String projectUri , String jdkPath , IProgressMonitor monitor ) throws CoreException , URISyntaxException {
434
616
IJavaProject javaProject = ProjectCommand .getJavaProjectFromUri (projectUri );
435
- IClasspathEntry [] originalClasspathEntries = javaProject .getRawClasspath ();
436
- IClasspathAttribute [] extraAttributes = null ;
437
617
List <IClasspathEntry > newClasspathEntries = new ArrayList <>();
438
- for (IClasspathEntry entry : originalClasspathEntries ) {
439
- if (entry .getEntryKind () == IClasspathEntry .CPE_CONTAINER &&
440
- entry .getPath ().toString ().startsWith ("org.eclipse.jdt.launching.JRE_CONTAINER" )) {
441
- extraAttributes = entry .getExtraAttributes ();
442
- } else {
443
- newClasspathEntries .add (entry );
618
+ try {
619
+ for (IClasspathEntry entry : javaProject .getRawClasspath ()) {
620
+ if (entry .getEntryKind () == IClasspathEntry .CPE_CONTAINER &&
621
+ entry .getPath ().toString ().startsWith (JavaRuntime .JRE_CONTAINER )) {
622
+ newClasspathEntries .add (getNewJdkEntry (javaProject , jdkPath ));
623
+ } else {
624
+ newClasspathEntries .add (entry );
625
+ }
444
626
}
627
+ javaProject .setRawClasspath (newClasspathEntries .toArray (IClasspathEntry []::new ), monitor );
628
+ } catch (CoreException e ) {
629
+ JavaLanguageServerPlugin .log (e );
630
+ return new JdkUpdateResult (false , e .getMessage ());
445
631
}
632
+ return new JdkUpdateResult (true , jdkPath );
633
+ }
446
634
635
+ private static IClasspathEntry getNewJdkEntry (IJavaProject javaProject , String jdkPath ) throws CoreException {
447
636
IVMInstall vmInstall = getVmInstallByPath (jdkPath );
637
+ List <IClasspathAttribute > extraAttributes = new ArrayList <>();
448
638
if (vmInstall == null ) {
449
- JavaLanguageServerPlugin .log (new Status (IStatus .ERROR , IConstants .PLUGIN_ID , "The select JDK path is not valid." ));
450
- return new JdkUpdateResult (false , "The selected JDK path is not valid." );
639
+ throw new CoreException (new Status (IStatus .ERROR , IConstants .PLUGIN_ID , "The select JDK path is not valid." ));
640
+ }
641
+ if (javaProject .getOwnModuleDescription () != null ) {
642
+ extraAttributes .add (JavaCore .newClasspathAttribute (IClasspathAttribute .MODULE , "true" ));
451
643
}
452
- newClasspathEntries .add (JavaCore .newContainerEntry (
644
+
645
+ return JavaCore .newContainerEntry (
453
646
JavaRuntime .newJREContainerPath (vmInstall ),
454
647
ClasspathEntry .NO_ACCESS_RULES ,
455
- extraAttributes ,
648
+ extraAttributes . toArray ( IClasspathAttribute []:: new ) ,
456
649
false /*isExported*/
457
- ));
458
- javaProject .setRawClasspath (newClasspathEntries .toArray (IClasspathEntry []::new ), monitor );
459
- return new JdkUpdateResult (true , vmInstall .getInstallLocation ().getAbsolutePath ());
650
+ );
460
651
}
461
652
462
653
private static IVMInstall getVmInstallByPath (String path ) {
0 commit comments