Skip to content

Commit

Permalink
Use linked resource instead of filesystem
Browse files Browse the repository at this point in the history
For all metadata files, except the .project (which may be harder to
move).
This also copies some logic from the filesystem to ProjectsManager. We
cannot have the filesystem bundle requiring and referencing the main
jdt.ls bundle as this would cause classloading error.
  • Loading branch information
mickaelistria committed Jun 5, 2023
1 parent 0e04fdd commit 98a4eb6
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.eclipse.m2e.core.lifecyclemapping.model.PluginExecutionAction;
import org.eclipse.m2e.core.project.IMavenProjectImportResult;
import org.eclipse.m2e.core.project.IProjectConfigurationManager;
import org.eclipse.m2e.core.project.IProjectCreationListener;
import org.eclipse.m2e.core.project.LocalProjectScanner;
import org.eclipse.m2e.core.project.MavenProjectInfo;
import org.eclipse.m2e.core.project.ProjectImportConfiguration;
Expand Down Expand Up @@ -208,6 +209,14 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException, Op
}
if (!toImport.isEmpty()) {
ProjectImportConfiguration importConfig = new ProjectImportConfiguration();
IProjectCreationListener linkFoldersUponProjectCreation = ProjectsManager.generatesMetadataFilesAtProjectRoot() ? null :
p -> {
try {
ProjectsManager.linkResources(p);
} catch (CoreException ex) {
JavaLanguageServerPlugin.logException(ex);
}
};
if (toImport.size() > artifactIds.size()) {
// Ensure project name is unique when same artifactId
importConfig.setProjectNameTemplate(DUPLICATE_ARTIFACT_TEMPLATE);
Expand All @@ -226,7 +235,7 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException, Op
while (i++ < MAX_PROJECTS_TO_IMPORT && iter.hasNext()) {
importPartial.add(iter.next());
}
List<IMavenProjectImportResult> result = configurationManager.importProjects(importPartial, importConfig, monitor2.split(MAX_PROJECTS_TO_IMPORT));
List<IMavenProjectImportResult> result = configurationManager.importProjects(importPartial, importConfig, linkFoldersUponProjectCreation, monitor2.split(MAX_PROJECTS_TO_IMPORT));
results.addAll(result);
monitor2.setWorkRemaining(toImport.size() * 2 - it * MAX_PROJECTS_TO_IMPORT);
}
Expand All @@ -238,7 +247,7 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException, Op
updateProjects(imported, lastWorkspaceStateSaved, monitor2.split(projects.size()));
monitor2.done();
} else {
configurationManager.importProjects(toImport, importConfig, subMonitor.split(75));
configurationManager.importProjects(toImport, importConfig, linkFoldersUponProjectCreation, subMonitor.split(75));
}
}
subMonitor.setWorkRemaining(20);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

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

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -29,9 +31,11 @@
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.internal.preferences.EclipsePreferences;
import org.eclipse.core.internal.resources.CharsetManager;
import org.eclipse.core.internal.resources.Workspace;
import org.eclipse.core.resources.FileInfoMatcherDescription;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
Expand All @@ -52,6 +56,7 @@
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
Expand Down Expand Up @@ -343,6 +348,45 @@ public static IProject createJavaProject(IProject project, IProgressMonitor moni
return createJavaProject(project, null, "src", "bin", monitor);
}

/*
* ⚠ These value is duplicated in ProjectsManager as both bundles must remain independent,
* but the same dir should be used for .project or .settings/.classpath.
* So when updating one, think about updating the other.
*/
public static final String GENERATES_METADATA_FILES_AT_PROJECT_ROOT = "java.import.generatesMetadataFilesAtProjectRoot";
public static final IPath METADATA_FOLDER_PATH = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".projects");

/**
* Check whether the metadata files needs to be generated at project root.
*/
public static boolean generatesMetadataFilesAtProjectRoot() {
String property = System.getProperty(GENERATES_METADATA_FILES_AT_PROJECT_ROOT);
if (property == null) {
return true;
}
return Boolean.parseBoolean(property);
}

/**
* Get the redirected path of the input path. The path will be redirected to
* the workspace's metadata folder ({@link JLSFsUtils#METADATA_FOLDER_PATH}).
* @param projectName name of the project.
* @param path path needs to be redirected.
* @return the redirected path.
*/
public static IPath getMetaDataFilePath(String projectName, IPath path) {
if (path.segmentCount() == 1) {
return METADATA_FOLDER_PATH.append(projectName).append(path);
}

String lastSegment = path.lastSegment();
if (IProjectDescription.DESCRIPTION_FILE_NAME.equals(lastSegment)) {
return METADATA_FOLDER_PATH.append(projectName).append(lastSegment);
}

return null;
}

public static IProject createJavaProject(IProject project, IPath projectLocation, String src, String bin, IProgressMonitor monitor) throws CoreException, OperationCanceledException {
if (project.exists()) {
return project;
Expand All @@ -355,6 +399,9 @@ public static IProject createJavaProject(IProject project, IPath projectLocation
}
project.create(description, monitor);
project.open(monitor);
if (!generatesMetadataFilesAtProjectRoot() && !DEFAULT_PROJECT_NAME.equals(project.getName())) {
linkResources(project);
}

//Turn into Java project
description = project.getDescription();
Expand Down Expand Up @@ -396,6 +443,43 @@ public static IProject createJavaProject(IProject project, IPath projectLocation
return project;
}

public static void linkResources(IProject project) throws CoreException {
IFile classpathFile = project.getFile(IJavaProject.CLASSPATH_FILE_NAME);
if (!classpathFile.exists()) {
File diskFile = getMetaDataFilePath(classpathFile.getProject().getName(), classpathFile.getProjectRelativePath()).toFile();
if (!diskFile.exists()) {
try {
diskFile.getParentFile().mkdirs();
diskFile.createNewFile();
} catch (IOException ex) {
throw new CoreException(Status.error(diskFile + " cannot be created", ex)); //$NON-NLS-1$
}
}
classpathFile.createLink(diskFile.toURI(), IResource.NONE, new NullProgressMonitor());
classpathFile.setContents(new ByteArrayInputStream("<classpath/>".getBytes()), 0, null);
}
IFile factorypathFile = project.getFile(".factorypath");
if (!factorypathFile.exists()) {
File diskFile = getMetaDataFilePath(classpathFile.getProject().getName(), factorypathFile.getProjectRelativePath()).toFile();
if (!diskFile.exists()) {
try {
diskFile.getParentFile().mkdirs();
diskFile.createNewFile();
} catch (IOException ex) {
throw new CoreException(Status.error(diskFile + " cannot be created", ex)); //$NON-NLS-1$
}
}
factorypathFile.createLink(diskFile.toURI(), IResource.ALLOW_MISSING_LOCAL, new NullProgressMonitor());
factorypathFile.setContents(new ByteArrayInputStream("<factorypath/>".getBytes()), 0, null);
}
IFolder settingsFolder = project.getFolder(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME);
if (!settingsFolder.exists()) {
File diskFolder = getMetaDataFilePath(classpathFile.getProject().getName(), settingsFolder.getProjectRelativePath()).toFile();
diskFolder.mkdirs();
settingsFolder.createLink(diskFolder.toURI(), IResource.ALLOW_MISSING_LOCAL, new NullProgressMonitor());
}
}

@Override
public Job updateProject(IProject project, boolean force) {
if (project == null || ProjectUtils.isInternalBuildSupport(BuildSupportManager.find(project).orElse(null))) {
Expand Down Expand Up @@ -426,6 +510,7 @@ public IStatus runInWorkspace(IProgressMonitor monitor) {
updateEncoding(monitor);
project.deleteMarkers(BUILD_FILE_MARKER_TYPE, false, IResource.DEPTH_ONE);
long elapsed = System.currentTimeMillis() - start;
replaceLinkedMetadataWithLocal(project);
JavaLanguageServerPlugin.logInfo("Updated " + projectName + " in " + elapsed + " ms");
} catch (Exception e) {
String msg = "Error updating " + projectName;
Expand Down Expand Up @@ -625,5 +710,12 @@ public void reportProjectsStatus() {
JavaLanguageServerPlugin.sendStatus(ServiceStatus.ProjectStatus, "OK");
}
}

private void replaceLinkedMetadataWithLocal(IProject p) throws CoreException {
if (new File(p.getLocation().toFile(), IJavaProject.CLASSPATH_FILE_NAME).exists() &&
p.getFile(IJavaProject.CLASSPATH_FILE_NAME).isLinked()) {
p.getFile(IJavaProject.CLASSPATH_FILE_NAME).delete(false, false, null);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.internal.filesystem.local.LocalFile;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
Expand Down Expand Up @@ -56,11 +57,9 @@ public String[] childNames(int options, IProgressMonitor monitor) {
}

Set<String> childNameSet = new LinkedHashSet<>(Arrays.asList(childNames));
for (String fileName : JLSFsUtils.METADATA_NAMES) {
if (!childNameSet.contains(fileName) &&
JLSFsUtils.METADATA_FOLDER_PATH.append(projectName).append(fileName).toFile().exists()) {
childNameSet.add(fileName);
}
if (!childNameSet.contains(IProjectDescription.DESCRIPTION_FILE_NAME) &&
JLSFsUtils.METADATA_FOLDER_PATH.append(projectName).append(IProjectDescription.DESCRIPTION_FILE_NAME).toFile().exists()) {
childNameSet.add(IProjectDescription.DESCRIPTION_FILE_NAME);
}

return childNameSet.toArray(String[]::new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@

/**
* JDT.LS's own implementation of file system to handle the 'file' scheme uri.
* The purpose of this implementation is to allow the project metadata files (.project, .classpath, .settings/)
* can be persisted out of the project root.
* The purpose of this implementation is to allow the .project files can be
* persisted out of the project root, since for the moment using linked resources
* seem not possible.
*/
public class JLSFileSystem extends LocalFileSystem {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,34 @@

package org.eclipse.jdt.ls.core.internal.filesystem;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import org.eclipse.core.internal.preferences.EclipsePreferences;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IJavaProject;

/**
* Utilities of the file system implementation.
*/
public class JLSFsUtils {
private JLSFsUtils() {}

static final IPath METADATA_FOLDER_PATH = ResourcesPlugin.getPlugin().getStateLocation().append(".projects");

/**
* The system property key to specify the file system mode.
*/
static final String GENERATES_METADATA_FILES_AT_PROJECT_ROOT = "java.import.generatesMetadataFilesAtProjectRoot";

static final String FACTORY_PATH = ".factorypath";

* ⚠ This value is duplicated in ProjectsManager as both bundles must remain independent,
* but the same dir should be used for .project or .settings/.classpath.
* So when updating one, think about updating the other.
**/
static final IPath METADATA_FOLDER_PATH = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".projects");;
/**
* The metadata files
* The system property key to specify the file system mode.
*
* This value is duplicated in ProjectsManager as both bundles must remain independent,
* but the same dir should be used for .project or .settings/.classpath.
* So when updating one, think about updating the other.
*/
static final Set<String> METADATA_NAMES = new HashSet<>(Arrays.asList(
IProjectDescription.DESCRIPTION_FILE_NAME,
EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME,
IJavaProject.CLASSPATH_FILE_NAME,
FACTORY_PATH
));
static final String GENERATES_METADATA_FILES_AT_PROJECT_ROOT = "java.import.generatesMetadataFilesAtProjectRoot";

/**
* Determine whether the resource should be stored in workspace's metadata folder.
Expand Down Expand Up @@ -77,9 +68,6 @@ static boolean shouldStoreInMetadataArea(IPath location) {
// do not redirect if the file already exists on disk
if (location.toFile().exists()) {
return false;
} else if (location.lastSegment().endsWith(EclipsePreferences.PREFS_FILE_EXTENSION)) {
location = location.removeLastSegments(1);
return !location.toFile().exists();
}

return true;
Expand All @@ -91,23 +79,9 @@ static boolean shouldStoreInMetadataArea(IPath location) {
* @return whether the given location points to a metadata file.
*/
static boolean isProjectMetadataFile(IPath location) {
if (location == null || location.segmentCount() < 2) {
return false;
}

if (location.lastSegment().endsWith(EclipsePreferences.PREFS_FILE_EXTENSION)) {
location = location.removeLastSegments(1);
}

if (location.segmentCount() < 2) {
return false;
}

if (!METADATA_NAMES.contains(location.lastSegment())) {
return false;
}

return true;
return location != null && //
location.segmentCount() != 2 && //
IProjectDescription.DESCRIPTION_FILE_NAME.equals(location.lastSegment());
}

/**
Expand All @@ -116,9 +90,6 @@ static boolean isProjectMetadataFile(IPath location) {
* @param filePath the file path.
*/
static IPath getContainerPath(IPath filePath) {
if (filePath.lastSegment().endsWith(EclipsePreferences.PREFS_FILE_EXTENSION)) {
filePath = filePath.removeLastSegments(1);
}
return filePath.removeLastSegments(1);
}

Expand Down Expand Up @@ -152,12 +123,8 @@ static IPath getMetaDataFilePath(String projectName, IPath path) {
}

String lastSegment = path.lastSegment();
if (METADATA_NAMES.contains(lastSegment)) {
if (IProjectDescription.DESCRIPTION_FILE_NAME.equals(lastSegment)) {
return METADATA_FOLDER_PATH.append(projectName).append(lastSegment);
} else if (lastSegment.endsWith(EclipsePreferences.PREFS_FILE_EXTENSION)) {
return METADATA_FOLDER_PATH.append(projectName)
.append(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME)
.append(lastSegment);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.managers.AbstractProjectsManagerBasedTest;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManagerTest;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager.CHANGE_TYPE;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -51,7 +53,7 @@ public static Collection<String> data(){

@Before
public void setUp() {
System.setProperty(JLSFsUtils.GENERATES_METADATA_FILES_AT_PROJECT_ROOT, fsMode);
System.setProperty(ProjectsManager.GENERATES_METADATA_FILES_AT_PROJECT_ROOT, fsMode);
}

@Test
Expand Down
Loading

0 comments on commit 98a4eb6

Please sign in to comment.