Skip to content

Commit 8b5604d

Browse files
committed
Use linked resource instead of filesystem
For all metadata files, except the .project (which may be harder to move), use linked resources instead of filesystem hack which can return invalid locations through standard API. This copies some logic from the org.eclipse.jdt.ls.filesystem to ProjectsManager, because we cannot have the filesystem bundle requiring and referencing the main jdt.ls bundle as this would cause bundle/class cycle and loading error. This also remove the InvisibleProjectMetadataTest.testMetadataFileLocation() test because creating a new project in Eclipse workspace now enforces creation of a .settings/org.eclipse.core.resources.prefs file. Where the metadata for the invisible project is stored doesn't matter as it is internal anyway. Also removes some usage of gson types which can cause conflicts when multiple versions are loaded.
1 parent 810f806 commit 8b5604d

File tree

14 files changed

+324
-151
lines changed

14 files changed

+324
-151
lines changed

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporter.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import java.util.ArrayList;
2929
import java.util.Arrays;
3030
import java.util.Collection;
31+
import java.util.Dictionary;
3132
import java.util.HashSet;
33+
import java.util.Hashtable;
3234
import java.util.LinkedList;
3335
import java.util.List;
3436
import java.util.Map;
@@ -48,6 +50,7 @@
4850
import org.eclipse.buildship.core.internal.DefaultGradleBuild;
4951
import org.eclipse.buildship.core.internal.preferences.PersistentModel;
5052
import org.eclipse.buildship.core.internal.util.gradle.GradleVersion;
53+
import org.eclipse.buildship.core.internal.workspace.WorkspaceOperations;
5154
import org.eclipse.core.resources.IFile;
5255
import org.eclipse.core.resources.IMarker;
5356
import org.eclipse.core.resources.IProject;
@@ -83,6 +86,9 @@
8386
import org.eclipse.lsp4j.MessageType;
8487
import org.gradle.tooling.model.build.BuildEnvironment;
8588
import org.gradle.tooling.model.build.GradleEnvironment;
89+
import org.osgi.framework.BundleContext;
90+
import org.osgi.framework.Constants;
91+
import org.osgi.framework.ServiceReference;
8692

8793
/**
8894
* @author Fred Bricon
@@ -127,6 +133,17 @@ public class GradleProjectImporter extends AbstractProjectImporter {
127133
.replaceAll("\n", System.lineSeparator());
128134
//@formatter:on
129135

136+
137+
public GradleProjectImporter() {
138+
// replace the project creation in buildship to hook in usage of linked resources
139+
BundleContext context = CorePlugin.getInstance().getBundle().getBundleContext();
140+
ServiceReference<WorkspaceOperations> serviceReference = context.getServiceReference(WorkspaceOperations.class);
141+
Dictionary<String, Object> props = new Hashtable<>(1, 1);
142+
props.put(Constants.SERVICE_RANKING, 2);
143+
context.ungetService(serviceReference);
144+
context.registerService(WorkspaceOperations.class, new WorkspaceOperationsWithLink(), props);
145+
}
146+
130147
/* (non-Javadoc)
131148
* @see org.eclipse.jdt.ls.core.internal.managers.IProjectImporter#applies(org.eclipse.core.runtime.IProgressMonitor)
132149
*/

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/MavenProjectImporter.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.eclipse.m2e.core.lifecyclemapping.model.PluginExecutionAction;
5555
import org.eclipse.m2e.core.project.IMavenProjectImportResult;
5656
import org.eclipse.m2e.core.project.IProjectConfigurationManager;
57+
import org.eclipse.m2e.core.project.IProjectCreationListener;
5758
import org.eclipse.m2e.core.project.LocalProjectScanner;
5859
import org.eclipse.m2e.core.project.MavenProjectInfo;
5960
import org.eclipse.m2e.core.project.ProjectImportConfiguration;
@@ -208,6 +209,14 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException, Op
208209
}
209210
if (!toImport.isEmpty()) {
210211
ProjectImportConfiguration importConfig = new ProjectImportConfiguration();
212+
IProjectCreationListener linkFoldersUponProjectCreation = ProjectsManager.generatesMetadataFilesAtProjectRoot() ? null :
213+
p -> {
214+
try {
215+
ProjectsManager.linkResources(p);
216+
} catch (CoreException ex) {
217+
JavaLanguageServerPlugin.logException(ex);
218+
}
219+
};
211220
if (toImport.size() > artifactIds.size()) {
212221
// Ensure project name is unique when same artifactId
213222
importConfig.setProjectNameTemplate(DUPLICATE_ARTIFACT_TEMPLATE);
@@ -226,7 +235,7 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException, Op
226235
while (i++ < MAX_PROJECTS_TO_IMPORT && iter.hasNext()) {
227236
importPartial.add(iter.next());
228237
}
229-
List<IMavenProjectImportResult> result = configurationManager.importProjects(importPartial, importConfig, monitor2.split(MAX_PROJECTS_TO_IMPORT));
238+
List<IMavenProjectImportResult> result = configurationManager.importProjects(importPartial, importConfig, linkFoldersUponProjectCreation, monitor2.split(MAX_PROJECTS_TO_IMPORT));
230239
results.addAll(result);
231240
monitor2.setWorkRemaining(toImport.size() * 2 - it * MAX_PROJECTS_TO_IMPORT);
232241
}
@@ -238,7 +247,7 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException, Op
238247
updateProjects(imported, lastWorkspaceStateSaved, monitor2.split(projects.size()));
239248
monitor2.done();
240249
} else {
241-
configurationManager.importProjects(toImport, importConfig, subMonitor.split(75));
250+
configurationManager.importProjects(toImport, importConfig, linkFoldersUponProjectCreation, subMonitor.split(75));
242251
}
243252
}
244253
subMonitor.setWorkRemaining(20);

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
import static org.eclipse.jdt.ls.core.internal.JVMConfigurator.configureJVMSettings;
1717

18+
import java.io.ByteArrayInputStream;
1819
import java.io.File;
20+
import java.io.IOException;
1921
import java.net.URI;
2022
import java.util.ArrayList;
2123
import java.util.Arrays;
@@ -29,9 +31,11 @@
2931
import java.util.stream.Stream;
3032

3133
import org.apache.commons.lang3.StringUtils;
34+
import org.eclipse.core.internal.preferences.EclipsePreferences;
3235
import org.eclipse.core.internal.resources.CharsetManager;
3336
import org.eclipse.core.internal.resources.Workspace;
3437
import org.eclipse.core.resources.FileInfoMatcherDescription;
38+
import org.eclipse.core.resources.IFile;
3539
import org.eclipse.core.resources.IFolder;
3640
import org.eclipse.core.resources.IMarker;
3741
import org.eclipse.core.resources.IProject;
@@ -52,6 +56,7 @@
5256
import org.eclipse.core.runtime.IProgressMonitor;
5357
import org.eclipse.core.runtime.IStatus;
5458
import org.eclipse.core.runtime.MultiStatus;
59+
import org.eclipse.core.runtime.NullProgressMonitor;
5560
import org.eclipse.core.runtime.OperationCanceledException;
5661
import org.eclipse.core.runtime.Platform;
5762
import org.eclipse.core.runtime.Status;
@@ -343,6 +348,45 @@ public static IProject createJavaProject(IProject project, IProgressMonitor moni
343348
return createJavaProject(project, null, "src", "bin", monitor);
344349
}
345350

351+
/*
352+
* ⚠ These value is duplicated in ProjectsManager as both bundles must remain independent,
353+
* but the same dir should be used for .project or .settings/.classpath.
354+
* So when updating one, think about updating the other.
355+
*/
356+
public static final String GENERATES_METADATA_FILES_AT_PROJECT_ROOT = "java.import.generatesMetadataFilesAtProjectRoot";
357+
public static final IPath METADATA_FOLDER_PATH = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".projects");
358+
359+
/**
360+
* Check whether the metadata files needs to be generated at project root.
361+
*/
362+
public static boolean generatesMetadataFilesAtProjectRoot() {
363+
String property = System.getProperty(GENERATES_METADATA_FILES_AT_PROJECT_ROOT);
364+
if (property == null) {
365+
return true;
366+
}
367+
return Boolean.parseBoolean(property);
368+
}
369+
370+
/**
371+
* Get the redirected path of the input path. The path will be redirected to
372+
* the workspace's metadata folder ({@link JLSFsUtils#METADATA_FOLDER_PATH}).
373+
* @param projectName name of the project.
374+
* @param path path needs to be redirected.
375+
* @return the redirected path.
376+
*/
377+
public static IPath getMetaDataFilePath(String projectName, IPath path) {
378+
if (path.segmentCount() == 1) {
379+
return METADATA_FOLDER_PATH.append(projectName).append(path);
380+
}
381+
382+
String lastSegment = path.lastSegment();
383+
if (IProjectDescription.DESCRIPTION_FILE_NAME.equals(lastSegment)) {
384+
return METADATA_FOLDER_PATH.append(projectName).append(lastSegment);
385+
}
386+
387+
return null;
388+
}
389+
346390
public static IProject createJavaProject(IProject project, IPath projectLocation, String src, String bin, IProgressMonitor monitor) throws CoreException, OperationCanceledException {
347391
if (project.exists()) {
348392
return project;
@@ -355,6 +399,9 @@ public static IProject createJavaProject(IProject project, IPath projectLocation
355399
}
356400
project.create(description, monitor);
357401
project.open(monitor);
402+
if (!generatesMetadataFilesAtProjectRoot()) {
403+
linkResources(project);
404+
}
358405

359406
//Turn into Java project
360407
description = project.getDescription();
@@ -396,6 +443,49 @@ public static IProject createJavaProject(IProject project, IPath projectLocation
396443
return project;
397444
}
398445

446+
public static void linkResources(IProject project) throws CoreException {
447+
{
448+
IFolder settingsFolder = project.getFolder(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME);
449+
if (!settingsFolder.exists()) {
450+
File diskFolder = getMetaDataFilePath(settingsFolder.getProject().getName(), settingsFolder.getProjectRelativePath()).toFile();
451+
diskFolder.mkdirs();
452+
settingsFolder.createLink(diskFolder.toURI(), IResource.NONE, new NullProgressMonitor());
453+
}
454+
}
455+
{
456+
IFile classpathFile = project.getFile(IJavaProject.CLASSPATH_FILE_NAME);
457+
if (!classpathFile.exists()) {
458+
File diskFile = getMetaDataFilePath(classpathFile.getProject().getName(), classpathFile.getProjectRelativePath()).toFile();
459+
if (!diskFile.exists()) {
460+
try {
461+
diskFile.getParentFile().mkdirs();
462+
diskFile.createNewFile();
463+
} catch (IOException ex) {
464+
throw new CoreException(Status.error(diskFile + " cannot be created", ex)); //$NON-NLS-1$
465+
}
466+
}
467+
classpathFile.createLink(diskFile.toURI(), IResource.NONE, new NullProgressMonitor());
468+
classpathFile.setContents(new ByteArrayInputStream("<classpath/>".getBytes()), 0, null);
469+
}
470+
}
471+
{
472+
IFile factorypathFile = project.getFile(".factorypath");
473+
if (!factorypathFile.exists()) {
474+
File diskFile = getMetaDataFilePath(factorypathFile.getProject().getName(), factorypathFile.getProjectRelativePath()).toFile();
475+
if (!diskFile.exists()) {
476+
try {
477+
diskFile.getParentFile().mkdirs();
478+
diskFile.createNewFile();
479+
} catch (IOException ex) {
480+
throw new CoreException(Status.error(diskFile + " cannot be created", ex)); //$NON-NLS-1$
481+
}
482+
}
483+
factorypathFile.createLink(diskFile.toURI(), IResource.NONE, new NullProgressMonitor());
484+
factorypathFile.setContents(new ByteArrayInputStream("<factorypath/>".getBytes()), 0, null);
485+
}
486+
}
487+
}
488+
399489
@Override
400490
public Job updateProject(IProject project, boolean force) {
401491
if (project == null || ProjectUtils.isInternalBuildSupport(BuildSupportManager.find(project).orElse(null))) {
@@ -426,6 +516,7 @@ public IStatus runInWorkspace(IProgressMonitor monitor) {
426516
updateEncoding(monitor);
427517
project.deleteMarkers(BUILD_FILE_MARKER_TYPE, false, IResource.DEPTH_ONE);
428518
long elapsed = System.currentTimeMillis() - start;
519+
replaceLinkedMetadataWithLocal(project);
429520
JavaLanguageServerPlugin.logInfo("Updated " + projectName + " in " + elapsed + " ms");
430521
} catch (Exception e) {
431522
String msg = "Error updating " + projectName;
@@ -625,5 +716,12 @@ public void reportProjectsStatus() {
625716
JavaLanguageServerPlugin.sendStatus(ServiceStatus.ProjectStatus, "OK");
626717
}
627718
}
719+
720+
private void replaceLinkedMetadataWithLocal(IProject p) throws CoreException {
721+
if (new File(p.getLocation().toFile(), IJavaProject.CLASSPATH_FILE_NAME).exists() &&
722+
p.getFile(IJavaProject.CLASSPATH_FILE_NAME).isLinked()) {
723+
p.getFile(IJavaProject.CLASSPATH_FILE_NAME).delete(false, false, null);
724+
}
725+
}
628726

629727
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License 2.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-2.0
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*******************************************************************************/
10+
package org.eclipse.jdt.ls.core.internal.managers;
11+
12+
import java.io.File;
13+
import java.util.List;
14+
import java.util.Map;
15+
16+
import org.eclipse.buildship.core.internal.workspace.DefaultWorkspaceOperations;
17+
import org.eclipse.buildship.core.internal.workspace.WorkspaceOperations;
18+
import org.eclipse.core.resources.IProject;
19+
import org.eclipse.core.resources.IProjectDescription;
20+
import org.eclipse.core.runtime.CoreException;
21+
import org.eclipse.core.runtime.IProgressMonitor;
22+
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
23+
24+
import com.google.common.base.Optional;
25+
import com.google.common.collect.ImmutableList;
26+
27+
public class WorkspaceOperationsWithLink implements WorkspaceOperations {
28+
29+
private final WorkspaceOperations delegate = new DefaultWorkspaceOperations();
30+
31+
@Override
32+
public ImmutableList<IProject> getAllProjects() {
33+
return delegate.getAllProjects();
34+
}
35+
36+
@Override
37+
public Optional<IProject> findProjectByName(String name) {
38+
return delegate.findProjectByName(name);
39+
}
40+
41+
@Override
42+
public Optional<IProject> findProjectByLocation(File location) {
43+
return delegate.findProjectByLocation(location);
44+
}
45+
46+
@Override
47+
public Optional<IProjectDescription> findProjectDescriptor(File location, IProgressMonitor monitor) {
48+
return delegate.findProjectDescriptor(location, monitor);
49+
}
50+
51+
@Override
52+
public IProject createProject(String name, File location, List<String> natureIds, IProgressMonitor monitor) {
53+
IProject res = delegate.createProject(name, location, natureIds, monitor);
54+
if (!ProjectsManager.generatesMetadataFilesAtProjectRoot()) {
55+
try {
56+
ProjectsManager.linkResources(res);
57+
} catch (CoreException ex) {
58+
JavaLanguageServerPlugin.logException(ex);
59+
}
60+
}
61+
return res;
62+
}
63+
64+
@Override
65+
public IProject includeProject(IProjectDescription projectDescription, List<String> extraNatureIds, IProgressMonitor monitor) {
66+
return delegate.includeProject(projectDescription, extraNatureIds, monitor);
67+
}
68+
69+
@Override
70+
public void refreshProject(IProject project, IProgressMonitor monitor) {
71+
delegate.refreshProject(project, monitor);
72+
}
73+
74+
@Override
75+
public void addNature(IProject project, String natureId, IProgressMonitor monitor) {
76+
delegate.addNature(project, natureId, monitor);
77+
}
78+
79+
@Override
80+
public void removeNature(IProject project, String natureId, IProgressMonitor monitor) {
81+
delegate.removeNature(project, natureId, monitor);
82+
}
83+
84+
@Override
85+
public void addBuildCommand(IProject project, String name, Map<String, String> arguments, IProgressMonitor monitor) {
86+
delegate.addBuildCommand(project, name, arguments, monitor);
87+
}
88+
89+
@Override
90+
public void removeBuildCommand(IProject project, String name, IProgressMonitor monitor) {
91+
delegate.removeBuildCommand(project, name, monitor);
92+
}
93+
94+
@Override
95+
public void validateProjectName(String name, File location) {
96+
delegate.validateProjectName(name, location);
97+
}
98+
99+
@Override
100+
public IProject renameProject(IProject project, String newName, IProgressMonitor monitor) {
101+
return delegate.renameProject(project, newName, monitor);
102+
}
103+
104+
@Override
105+
public boolean isNatureRecognizedByEclipse(String natureId) {
106+
return delegate.isNatureRecognizedByEclipse(natureId);
107+
}
108+
109+
@Override
110+
public boolean isWtpInstalled() {
111+
return delegate.isWtpInstalled();
112+
}
113+
}

0 commit comments

Comments
 (0)