14
14
package org .eclipse .jdt .ls .core .internal .commands ;
15
15
16
16
import java .io .File ;
17
+ import java .io .FileOutputStream ;
18
+ import java .io .IOException ;
17
19
import java .net .URI ;
18
20
import java .net .URISyntaxException ;
19
21
import java .util .ArrayDeque ;
28
30
import java .util .List ;
29
31
import java .util .Map ;
30
32
import java .util .Optional ;
33
+ import java .util .Map .Entry ;
34
+ import java .util .Objects ;
31
35
import java .util .stream .Stream ;
32
36
33
37
import org .eclipse .core .resources .IContainer ;
46
50
import org .eclipse .core .runtime .jobs .Job ;
47
51
import org .eclipse .debug .core .ILaunchConfiguration ;
48
52
import org .eclipse .jdt .core .IClasspathAttribute ;
53
+ import org .eclipse .jdt .core .IClasspathContainer ;
49
54
import org .eclipse .jdt .core .IClasspathEntry ;
50
55
import org .eclipse .jdt .core .ICompilationUnit ;
51
56
import org .eclipse .jdt .core .IJavaElement ;
57
+ import org .eclipse .jdt .core .IJavaModelStatus ;
52
58
import org .eclipse .jdt .core .IJavaProject ;
53
59
import org .eclipse .jdt .core .IParent ;
54
60
import org .eclipse .jdt .core .IType ;
71
77
import org .eclipse .jdt .ls .core .internal .managers .ProjectsManager ;
72
78
import org .eclipse .lsp4j .Location ;
73
79
import org .eclipse .lsp4j .SymbolInformation ;
80
+ import org .eclipse .m2e .jdt .IClasspathManager ;
81
+ import org .eclipse .m2e .jdt .MavenJdtPlugin ;
82
+ import org .eclipse .m2e .jdt .internal .BuildPathManager ;
83
+ import org .eclipse .m2e .jdt .internal .MavenClasspathContainer ;
84
+ import org .eclipse .m2e .jdt .internal .MavenClasspathContainerSaveHelper ;
74
85
75
86
public class ProjectCommand {
76
87
@@ -147,22 +158,47 @@ public static Map<String, Object> getProjectSettings(String uri, List<String> se
147
158
settings .putIfAbsent (key , referencedLibraries );
148
159
break ;
149
160
case CLASSPATH_ENTRIES :
150
- IClasspathEntry [] entries = javaProject .getRawClasspath ();
161
+ List <IClasspathEntry > entriesToBeScan = new LinkedList <>();
162
+ Collections .addAll (entriesToBeScan , javaProject .getRawClasspath ());
151
163
List <ProjectClasspathEntry > classpathEntries = new LinkedList <>();
152
- for (IClasspathEntry entry : entries ) {
164
+ for (int i = 0 ; i < entriesToBeScan .size (); i ++) {
165
+ IClasspathEntry entry = entriesToBeScan .get (i );
153
166
IPath path = entry .getPath ();
154
167
IPath output = entry .getOutputLocation ();
155
- if (entry .getEntryKind () == IClasspathEntry .CPE_SOURCE ) {
168
+ int entryKind = entry .getEntryKind ();
169
+ if (entryKind == IClasspathEntry .CPE_SOURCE ) {
170
+ IPath relativePath = path .makeRelativeTo (project .getFullPath ());
171
+ if (relativePath .isEmpty ()) {
172
+ continue ; // A valid relative source path should not be empty.
173
+ }
156
174
path = project .getFolder (path .makeRelativeTo (project .getFullPath ())).getLocation ();
157
175
if (output != null ) {
158
176
output = project .getFolder (output .makeRelativeTo (project .getFullPath ())).getLocation ();
159
177
}
178
+ } else if (entryKind == IClasspathEntry .CPE_CONTAINER ) {
179
+ IPath containerPath = entry .getPath ();
180
+ // skip JRE container, since it's already handled in VM_LOCATION.
181
+ if (!containerPath .toString ().startsWith (JavaRuntime .JRE_CONTAINER )) {
182
+ IClasspathContainer container = JavaCore .getClasspathContainer (containerPath , javaProject );
183
+ if (container != null ) {
184
+ entriesToBeScan .addAll (Arrays .asList (container .getClasspathEntries ()));
185
+ }
186
+ }
187
+ continue ;
188
+ } else if (entryKind == IClasspathEntry .CPE_LIBRARY ) {
189
+ if (!path .toFile ().exists ()) {
190
+ // the case when lib path is relative
191
+ path = project .getFile (path .makeRelativeTo (project .getFullPath ())).getLocation ();
192
+ }
193
+ if (!path .toFile ().exists ()) {
194
+ continue ;
195
+ }
160
196
}
161
197
Map <String , String > attributes = new HashMap <>();
162
198
for (IClasspathAttribute attribute : entry .getExtraAttributes ()) {
163
199
attributes .put (attribute .getName (), attribute .getValue ());
164
200
}
165
- classpathEntries .add (new ProjectClasspathEntry (entry . getEntryKind () , path .toOSString (),
201
+ classpathEntries .add (new ProjectClasspathEntry (entryKind , path .toOSString (),
166
202
output == null ? null : output .toOSString (), attributes ));
167
203
}
168
204
settings .putIfAbsent (key , classpathEntries );
@@ -175,6 +211,160 @@ public static Map<String, Object> getProjectSettings(String uri, List<String> se
175
211
return settings ;
176
212
}
177
213
214
+ /**
215
+ * Update the project classpath by the given classpath entries.
216
+ */
217
+ public static void updateClasspaths (String uri , List <ProjectClasspathEntry > entries , IProgressMonitor monitor ) throws CoreException , URISyntaxException {
218
+ IJavaProject javaProject = getJavaProjectFromUri (uri );
219
+ IProject project = javaProject .getProject ();
220
+ Map <IPath , IPath > sourceAndOutput = new HashMap <>();
221
+ List <IClasspathEntry > newEntries = new LinkedList <>();
222
+ List <IClasspathEntry > dependencyEntries = new LinkedList <>();
223
+ for (ProjectClasspathEntry entry : entries ) {
224
+ if (entry .getKind () == IClasspathEntry .CPE_SOURCE ) {
225
+ IPath path = project .getFolder (entry .getPath ()).getFullPath ();
226
+ IPath outputLocation = null ;
227
+ String output = entry .getOutput ();
228
+ if (output != null ) {
229
+ if ("." .equals (output )) {
230
+ outputLocation = project .getFullPath ();
231
+ } else {
232
+ outputLocation = project .getFolder (output ).getFullPath ();
233
+ }
234
+ }
235
+ sourceAndOutput .put (path , outputLocation );
236
+ } else if (entry .getKind () == IClasspathEntry .CPE_CONTAINER ) {
237
+ if (entry .getPath ().startsWith (JavaRuntime .JRE_CONTAINER )) {
238
+ String jdkPath = entry .getPath ().substring (JavaRuntime .JRE_CONTAINER .length ());
239
+ newEntries .add (getNewJdkEntry (javaProject , jdkPath ));
240
+ } else {
241
+ JavaLanguageServerPlugin .logInfo ("The container entry " + entry .getPath () + " is not supported to be updated." );
242
+ }
243
+ } else {
244
+ dependencyEntries .add (convertClasspathEntry (entry ));
245
+ }
246
+ }
247
+ IClasspathEntry [] sources = ProjectUtils .resolveSourceClasspathEntries (javaProject , sourceAndOutput , Collections .emptyList (), javaProject .getOutputLocation ());
248
+ newEntries .addAll (Arrays .asList (sources ));
249
+
250
+ // update maven classpath container since it's exposed from m2e.
251
+ // May consider extracting this operation to an API of IBuildSupport.
252
+ if (ProjectUtils .isMavenProject (project )) {
253
+ dependencyEntries = resolveMavenDependencyEntries (javaProject , dependencyEntries .toArray (IClasspathEntry []::new ), monitor );
254
+ }
255
+ newEntries .addAll (dependencyEntries );
256
+
257
+ IClasspathEntry [] rawClasspath = newEntries .toArray (IClasspathEntry []::new );
258
+ IJavaModelStatus checkStatus = ClasspathEntry .validateClasspath (javaProject , rawClasspath , javaProject .getOutputLocation ());
259
+ if (!checkStatus .isOK ()) {
260
+ throw new CoreException (checkStatus );
261
+ }
262
+ javaProject .setRawClasspath (rawClasspath , monitor );
263
+ }
264
+
265
+ /**
266
+ * Convert ProjectClasspathEntry to IClasspathEntry.
267
+ */
268
+ private static IClasspathEntry convertClasspathEntry (ProjectClasspathEntry entry ) {
269
+ List <IClasspathAttribute > attributes = new LinkedList <>();
270
+ if (entry .getAttributes () != null ) {
271
+ for (Entry <String , String > attributEntry : entry .getAttributes ().entrySet ()) {
272
+ attributes .add (JavaCore .newClasspathAttribute (attributEntry .getKey (), attributEntry .getValue ()));
273
+ }
274
+ }
275
+ switch (entry .getKind ()) {
276
+ case IClasspathEntry .CPE_CONTAINER :
277
+ return JavaCore .newContainerEntry (
278
+ IPath .fromOSString (entry .getPath ()),
279
+ ClasspathEntry .NO_ACCESS_RULES ,
280
+ attributes .toArray (IClasspathAttribute []::new ),
281
+ false
282
+ );
283
+ case IClasspathEntry .CPE_LIBRARY :
284
+ return JavaCore .newLibraryEntry (
285
+ IPath .fromOSString (entry .getPath ()),
286
+ null ,
287
+ null ,
288
+ ClasspathEntry .NO_ACCESS_RULES ,
289
+ attributes .toArray (IClasspathAttribute []::new ),
290
+ false
291
+ );
292
+ case IClasspathEntry .CPE_PROJECT :
293
+ return JavaCore .newProjectEntry (
294
+ IPath .fromOSString (entry .getPath ()),
295
+ ClasspathEntry .NO_ACCESS_RULES ,
296
+ false ,
297
+ attributes .toArray (IClasspathAttribute []::new ),
298
+ false
299
+ );
300
+ default :
301
+ return null ;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#updateClasspath()}
307
+ */
308
+ private static List <IClasspathEntry > resolveMavenDependencyEntries (IJavaProject javaProject , IClasspathEntry [] entries , IProgressMonitor monitor ) {
309
+ IProject project = javaProject .getProject ();
310
+ if (!ProjectUtils .isMavenProject (project )) {
311
+ JavaLanguageServerPlugin .logError ("Project '" + javaProject .getElementName () + "' is not a Maven project." );
312
+ return Collections .emptyList ();
313
+ }
314
+
315
+ List <IClasspathEntry > mavenEntries = new LinkedList <>();
316
+ List <IClasspathEntry > resolvedEntries = new LinkedList <>();
317
+ for (IClasspathEntry entry : entries ) {
318
+ if (isMavenEntry (entry )) {
319
+ mavenEntries .add (entry );
320
+ } else {
321
+ resolvedEntries .add (entry );
322
+ }
323
+ }
324
+ try {
325
+ IClasspathEntry containerEntry = BuildPathManager .getMavenContainerEntry (javaProject );
326
+ IPath path = containerEntry != null ? containerEntry .getPath () :
327
+ IPath .fromOSString (BuildPathManager .CONTAINER_ID );
328
+ IClasspathContainer container = new MavenClasspathContainer (path , entries );
329
+ JavaCore .setClasspathContainer (container .getPath (), new IJavaProject [] {javaProject },
330
+ new IClasspathContainer [] {container }, monitor );
331
+ saveContainerState (project , container );
332
+ resolvedEntries .add (JavaCore .newContainerEntry (path ));
333
+ } catch (JavaModelException e ) {
334
+ JavaLanguageServerPlugin .log (e );
335
+ }
336
+ return resolvedEntries ;
337
+ }
338
+
339
+ private static boolean isMavenEntry (IClasspathEntry entry ) {
340
+ for (IClasspathAttribute attribute : entry .getExtraAttributes ()) {
341
+ if (Objects .equals (IClasspathManager .POMDERIVED_ATTRIBUTE , attribute .getName ()) && Boolean .parseBoolean (attribute .getValue ())) {
342
+ return true ;
343
+ }
344
+ }
345
+ return false ;
346
+ }
347
+
348
+ /**
349
+ * See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#saveContainerState()}
350
+ */
351
+ private static void saveContainerState (IProject project , IClasspathContainer container ) {
352
+ File containerStateFile = getContainerStateFile (project );
353
+ try (FileOutputStream is = new FileOutputStream (containerStateFile )) {
354
+ new MavenClasspathContainerSaveHelper ().writeContainer (container , is );
355
+ } catch (IOException ex ) {
356
+ JavaLanguageServerPlugin .logException ("Can't save classpath container state for " + project .getName (), ex ); //$NON-NLS-1$
357
+ }
358
+ }
359
+
360
+ /**
361
+ * See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#getContainerStateFile()}
362
+ */
363
+ private static File getContainerStateFile (IProject project ) {
364
+ File stateLocationDir = MavenJdtPlugin .getDefault ().getStateLocation ().toFile ();
365
+ return new File (stateLocationDir , project .getName () + ".container" ); //$NON-NLS-1$
366
+ }
367
+
178
368
/**
179
369
* Updates the project source paths.
180
370
* @param uri Uri of the project.
@@ -432,31 +622,40 @@ public static SymbolInformation resolveWorkspaceSymbol(SymbolInformation request
432
622
433
623
public static JdkUpdateResult updateProjectJdk (String projectUri , String jdkPath , IProgressMonitor monitor ) throws CoreException , URISyntaxException {
434
624
IJavaProject javaProject = ProjectCommand .getJavaProjectFromUri (projectUri );
435
- IClasspathEntry [] originalClasspathEntries = javaProject .getRawClasspath ();
436
- IClasspathAttribute [] extraAttributes = null ;
437
625
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 );
626
+ try {
627
+ for (IClasspathEntry entry : javaProject .getRawClasspath ()) {
628
+ if (entry .getEntryKind () == IClasspathEntry .CPE_CONTAINER &&
629
+ entry .getPath ().toString ().startsWith (JavaRuntime .JRE_CONTAINER )) {
630
+ newClasspathEntries .add (getNewJdkEntry (javaProject , jdkPath ));
631
+ } else {
632
+ newClasspathEntries .add (entry );
633
+ }
444
634
}
635
+ javaProject .setRawClasspath (newClasspathEntries .toArray (IClasspathEntry []::new ), monitor );
636
+ } catch (CoreException e ) {
637
+ JavaLanguageServerPlugin .log (e );
638
+ return new JdkUpdateResult (false , e .getMessage ());
445
639
}
640
+ return new JdkUpdateResult (true , jdkPath );
641
+ }
446
642
643
+ private static IClasspathEntry getNewJdkEntry (IJavaProject javaProject , String jdkPath ) throws CoreException {
447
644
IVMInstall vmInstall = getVmInstallByPath (jdkPath );
645
+ List <IClasspathAttribute > extraAttributes = new ArrayList <>();
448
646
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." );
647
+ throw new CoreException (new Status (IStatus .ERROR , IConstants .PLUGIN_ID , "The select JDK path is not valid." ));
451
648
}
452
- newClasspathEntries .add (JavaCore .newContainerEntry (
649
+ if (javaProject .getOwnModuleDescription () != null ) {
650
+ extraAttributes .add (JavaCore .newClasspathAttribute (IClasspathAttribute .MODULE , "true" ));
651
+ }
652
+
653
+ return JavaCore .newContainerEntry (
453
654
JavaRuntime .newJREContainerPath (vmInstall ),
454
655
ClasspathEntry .NO_ACCESS_RULES ,
455
- extraAttributes ,
656
+ extraAttributes . toArray ( IClasspathAttribute []:: new ) ,
456
657
false /*isExported*/
457
- ));
458
- javaProject .setRawClasspath (newClasspathEntries .toArray (IClasspathEntry []::new ), monitor );
459
- return new JdkUpdateResult (true , vmInstall .getInstallLocation ().getAbsolutePath ());
658
+ );
460
659
}
461
660
462
661
private static IVMInstall getVmInstallByPath (String path ) {
0 commit comments