Skip to content

Commit

Permalink
Use linked resource instead of filesystem
Browse files Browse the repository at this point in the history
Use linked resources instead of filesystem hack as this one can return
invalid locations through standard API.

This removes the filesystem bundle, the logic is now moved in
ProjectManagers
and a workspace listener takes care of using linked resources for
metadata.

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 the internal project
is internal and not visible to user anyway.
  • Loading branch information
mickaelistria committed Nov 7, 2023
1 parent a31867d commit ef20a90
Show file tree
Hide file tree
Showing 25 changed files with 240 additions and 594 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*******************************************************************************
* Copyright (c) 2023 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.managers;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;

import org.eclipse.core.internal.preferences.EclipsePreferences;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;

class LinkResourceUtil {

private static final IPath METADATA_FOLDER_PATH = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".projects");

private static boolean isNewer(Path file, Instant instant) throws CoreException {
try {
BasicFileAttributes attributes = Files.getFileAttributeView(file, BasicFileAttributeView.class).readAttributes();
return attributes.creationTime().toInstant().isAfter(instant);
} catch (IOException ex) {
throw new CoreException(Status.error(ex.getMessage(), ex));
}
}

/**
* Get the redirected path of the input path. The path will be redirected to
* the workspace's metadata folder ({@link LinkResourceUtil#METADATA_FOLDER_PATH}).
* @param projectName name of the project.
* @param path path needs to be redirected.
* @return the redirected path.
*/
private 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;
}

private static void linkFolderIfNewer(IFolder settingsFolder, Instant instant) throws CoreException {
if (settingsFolder.isLinked()) {
return;
}
if (settingsFolder.exists() && !isNewer(settingsFolder.getLocation().toPath(), instant)) {
return;
}
if (!settingsFolder.exists()) {
// not existing yet, create link
File diskFolder = getMetaDataFilePath(settingsFolder.getProject().getName(), settingsFolder.getProjectRelativePath()).toFile();
diskFolder.mkdirs();
settingsFolder.createLink(diskFolder.toURI(), IResource.NONE, new NullProgressMonitor());
} else if (isNewer(settingsFolder.getLocation().toPath(), instant)) {
// already existing but not existing before import: move then link
File sourceFolder = settingsFolder.getLocation().toFile();
File targetFolder = getMetaDataFilePath(settingsFolder.getProject().getName(), settingsFolder.getProjectRelativePath()).toFile();
File parentTargetFolder = targetFolder.getParentFile();
if (!parentTargetFolder.isDirectory()) {
parentTargetFolder.mkdirs();
}
sourceFolder.renameTo(targetFolder);
settingsFolder.createLink(targetFolder.toURI(), IResource.REPLACE, new NullProgressMonitor());
}
}

private static void linkFileIfNewer(IFile metadataFile, Instant instant, String ifEmpty) throws CoreException {
if (metadataFile.isLinked()) {
return;
}
if (metadataFile.exists() && !isNewer(metadataFile.getLocation().toPath(), instant)) {
return;
}
File targetFile = getMetaDataFilePath(metadataFile.getProject().getName(), metadataFile.getProjectRelativePath()).toFile();
targetFile.getParentFile().mkdirs();
try {
if (metadataFile.exists()) {
Files.move(metadataFile.getLocation().toFile().toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} else {
Files.writeString(targetFile.toPath(), ifEmpty != null ? ifEmpty : "", StandardOpenOption.CREATE);
}
} catch (IOException ex) {
throw new CoreException(Status.error(ex.getMessage(), ex));
}
metadataFile.createLink(targetFile.toURI(), IResource.REPLACE, new NullProgressMonitor());
}

public static void linkMetadataResourcesIfNewer(IProject project, Instant instant) throws CoreException {
linkFileIfNewer(project.getFile(IProjectDescription.DESCRIPTION_FILE_NAME), instant, null);
linkFolderIfNewer(project.getFolder(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME), instant);
linkFileIfNewer(project.getFile(IJavaProject.CLASSPATH_FILE_NAME), instant, null);
linkFileIfNewer(project.getFile(".factorypath"), instant, "<factorypath/>");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@

import java.io.File;
import java.net.URI;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -145,6 +148,8 @@ private void updateEncoding(IProgressMonitor monitor) throws CoreException {
protected void importProjects(Collection<IPath> rootPaths, IProgressMonitor monitor) throws CoreException, OperationCanceledException {
SubMonitor subMonitor = SubMonitor.convert(monitor, rootPaths.size() * 100);
MultiStatus importStatusCollection = new MultiStatus(IConstants.PLUGIN_ID, -1, "Failed to import projects", null);
IProject[] alreadyExistingProjects = ProjectsManager.getWorkspaceRoot().getProjects();
Instant importSessionStart = Instant.now();
for (IPath rootPath : rootPaths) {
File rootFolder = rootPath.toFile();
try {
Expand All @@ -163,6 +168,18 @@ protected void importProjects(Collection<IPath> rootPaths, IProgressMonitor moni
JavaLanguageServerPlugin.logException("Failed to import projects", e);
}
}
if (!generatesMetadataFilesAtProjectRoot()) {
Set<IProject> newProjects = new HashSet<>(Arrays.asList(getWorkspaceRoot().getProjects()));
newProjects.removeAll(Arrays.asList(alreadyExistingProjects));
for (IProject project : newProjects) {
try {
LinkResourceUtil.linkMetadataResourcesIfNewer(project, importSessionStart);
} catch (CoreException e) {
importStatusCollection.add(e.getStatus());
JavaLanguageServerPlugin.logException("Failed to import projects", e);
}
}
}
if (!importStatusCollection.isOK()) {
throw new CoreException(importStatusCollection);
}
Expand Down Expand Up @@ -349,10 +366,29 @@ 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";

/**
* 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);
}

public static IProject createJavaProject(IProject project, IPath projectLocation, String src, String bin, IProgressMonitor monitor) throws CoreException, OperationCanceledException {
if (project.exists()) {
return project;
}
Instant creationRequestInstant = Instant.now();
JavaLanguageServerPlugin.logInfo("Creating the Java project " + project.getName());
//Create project
IProjectDescription description = ResourcesPlugin.getWorkspace().newProjectDescription(project.getName());
Expand Down Expand Up @@ -400,6 +436,10 @@ public static IProject createJavaProject(IProject project, IPath projectLocation
//Add JVM to project class path
javaProject.setRawClasspath(classpaths.toArray(new IClasspathEntry[0]), monitor);

if (!generatesMetadataFilesAtProjectRoot()) {
LinkResourceUtil.linkMetadataResourcesIfNewer(project, creationRequestInstant);
}

JavaLanguageServerPlugin.logInfo("Finished creating the Java project " + project.getName());
return project;
}
Expand Down Expand Up @@ -434,6 +474,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 @@ -641,6 +682,13 @@ 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);
}
}

class UpdateProjectsWorkspaceJob extends WorkspaceJob {

Expand Down
7 changes: 0 additions & 7 deletions org.eclipse.jdt.ls.filesystem/.classpath

This file was deleted.

45 changes: 0 additions & 45 deletions org.eclipse.jdt.ls.filesystem/.project

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

16 changes: 0 additions & 16 deletions org.eclipse.jdt.ls.filesystem/META-INF/MANIFEST.MF

This file was deleted.

6 changes: 0 additions & 6 deletions org.eclipse.jdt.ls.filesystem/build.properties

This file was deleted.

14 changes: 0 additions & 14 deletions org.eclipse.jdt.ls.filesystem/plugin.properties

This file was deleted.

10 changes: 0 additions & 10 deletions org.eclipse.jdt.ls.filesystem/plugin.xml

This file was deleted.

13 changes: 0 additions & 13 deletions org.eclipse.jdt.ls.filesystem/pom.xml

This file was deleted.

Loading

0 comments on commit ef20a90

Please sign in to comment.