Skip to content

Commit 100c00e

Browse files
committed
Use linked resource instead of filesystem
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.
1 parent a31867d commit 100c00e

File tree

25 files changed

+251
-594
lines changed

25 files changed

+251
-594
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
* https://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.io.IOException;
14+
import java.nio.file.Files;
15+
import java.nio.file.Path;
16+
import java.nio.file.StandardCopyOption;
17+
import java.nio.file.StandardOpenOption;
18+
import java.nio.file.attribute.BasicFileAttributeView;
19+
import java.time.Instant;
20+
import java.time.temporal.ChronoUnit;
21+
import java.util.stream.Stream;
22+
23+
import org.eclipse.core.internal.preferences.EclipsePreferences;
24+
import org.eclipse.core.resources.IFile;
25+
import org.eclipse.core.resources.IFolder;
26+
import org.eclipse.core.resources.IProject;
27+
import org.eclipse.core.resources.IProjectDescription;
28+
import org.eclipse.core.resources.IResource;
29+
import org.eclipse.core.resources.ResourcesPlugin;
30+
import org.eclipse.core.runtime.CoreException;
31+
import org.eclipse.core.runtime.IPath;
32+
import org.eclipse.core.runtime.NullProgressMonitor;
33+
import org.eclipse.core.runtime.Status;
34+
import org.eclipse.jdt.core.IJavaProject;
35+
36+
class LinkResourceUtil {
37+
38+
private static final IPath METADATA_FOLDER_PATH = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".projects");
39+
40+
private static boolean isNewer(Path file, Instant instant) throws CoreException {
41+
try {
42+
Instant creationInstant = Files.getFileAttributeView(file, BasicFileAttributeView.class).readAttributes().creationTime().toInstant();
43+
// match accuracy
44+
ChronoUnit smallestSupportedUnit = Stream.of(ChronoUnit.NANOS, ChronoUnit.MILLIS, ChronoUnit.SECONDS) // IMPORTANT: keeps units in Stream from finer to coarser
45+
.filter(creationInstant::isSupported).filter(instant::isSupported) //
46+
.findFirst().orElse(null);
47+
if (smallestSupportedUnit != null) {
48+
creationInstant.truncatedTo(smallestSupportedUnit);
49+
instant.truncatedTo(smallestSupportedUnit);
50+
} else {
51+
throw new CoreException(Status.error("No supported time unit!"));
52+
}
53+
return creationInstant.equals(instant) || creationInstant.isAfter(instant);
54+
} catch (IOException ex) {
55+
throw new CoreException(Status.error(ex.getMessage(), ex));
56+
}
57+
}
58+
59+
/**
60+
* Get the redirected path of the input path. The path will be redirected to
61+
* the workspace's metadata folder ({@link LinkResourceUtil#METADATA_FOLDER_PATH}).
62+
* @param projectName name of the project.
63+
* @param path path needs to be redirected.
64+
* @return the redirected path.
65+
*/
66+
private static IPath getMetaDataFilePath(String projectName, IPath path) {
67+
if (path.segmentCount() == 1) {
68+
return METADATA_FOLDER_PATH.append(projectName).append(path);
69+
}
70+
71+
String lastSegment = path.lastSegment();
72+
if (IProjectDescription.DESCRIPTION_FILE_NAME.equals(lastSegment)) {
73+
return METADATA_FOLDER_PATH.append(projectName).append(lastSegment);
74+
}
75+
76+
return null;
77+
}
78+
79+
private static void linkFolderIfNewer(IFolder settingsFolder, Instant instant) throws CoreException {
80+
if (settingsFolder.isLinked()) {
81+
return;
82+
}
83+
if (settingsFolder.exists() && !isNewer(settingsFolder.getLocation().toPath(), instant)) {
84+
return;
85+
}
86+
if (!settingsFolder.exists()) {
87+
// not existing yet, create link
88+
File diskFolder = getMetaDataFilePath(settingsFolder.getProject().getName(), settingsFolder.getProjectRelativePath()).toFile();
89+
diskFolder.mkdirs();
90+
settingsFolder.createLink(diskFolder.toURI(), IResource.NONE, new NullProgressMonitor());
91+
} else if (isNewer(settingsFolder.getLocation().toPath(), instant)) {
92+
// already existing but not existing before import: move then link
93+
File sourceFolder = settingsFolder.getLocation().toFile();
94+
File targetFolder = getMetaDataFilePath(settingsFolder.getProject().getName(), settingsFolder.getProjectRelativePath()).toFile();
95+
File parentTargetFolder = targetFolder.getParentFile();
96+
if (!parentTargetFolder.isDirectory()) {
97+
parentTargetFolder.mkdirs();
98+
}
99+
sourceFolder.renameTo(targetFolder);
100+
settingsFolder.createLink(targetFolder.toURI(), IResource.REPLACE, new NullProgressMonitor());
101+
}
102+
}
103+
104+
private static void linkFileIfNewer(IFile metadataFile, Instant instant, String ifEmpty) throws CoreException {
105+
if (metadataFile.isLinked()) {
106+
return;
107+
}
108+
if (metadataFile.exists() && !isNewer(metadataFile.getLocation().toPath(), instant)) {
109+
return;
110+
}
111+
File targetFile = getMetaDataFilePath(metadataFile.getProject().getName(), metadataFile.getProjectRelativePath()).toFile();
112+
targetFile.getParentFile().mkdirs();
113+
try {
114+
if (metadataFile.exists()) {
115+
Files.move(metadataFile.getLocation().toFile().toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
116+
} else {
117+
Files.writeString(targetFile.toPath(), ifEmpty != null ? ifEmpty : "", StandardOpenOption.CREATE);
118+
}
119+
} catch (IOException ex) {
120+
throw new CoreException(Status.error(ex.getMessage(), ex));
121+
}
122+
metadataFile.createLink(targetFile.toURI(), IResource.REPLACE, new NullProgressMonitor());
123+
}
124+
125+
public static void linkMetadataResourcesIfNewer(IProject project, Instant instant) throws CoreException {
126+
linkFileIfNewer(project.getFile(IProjectDescription.DESCRIPTION_FILE_NAME), instant, null);
127+
linkFolderIfNewer(project.getFolder(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME), instant);
128+
linkFileIfNewer(project.getFile(IJavaProject.CLASSPATH_FILE_NAME), instant, null);
129+
linkFileIfNewer(project.getFile(".factorypath"), instant, "<factorypath/>");
130+
}
131+
132+
}

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

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@
1717

1818
import java.io.File;
1919
import java.net.URI;
20+
import java.time.Instant;
2021
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.Collection;
2324
import java.util.HashMap;
25+
import java.util.HashSet;
2426
import java.util.List;
2527
import java.util.Map;
28+
import java.util.Map.Entry;
2629
import java.util.Objects;
2730
import java.util.Optional;
31+
import java.util.Set;
2832
import java.util.TreeMap;
29-
import java.util.Map.Entry;
3033
import java.util.stream.Collectors;
3134
import java.util.stream.Stream;
3235

@@ -145,6 +148,8 @@ private void updateEncoding(IProgressMonitor monitor) throws CoreException {
145148
protected void importProjects(Collection<IPath> rootPaths, IProgressMonitor monitor) throws CoreException, OperationCanceledException {
146149
SubMonitor subMonitor = SubMonitor.convert(monitor, rootPaths.size() * 100);
147150
MultiStatus importStatusCollection = new MultiStatus(IConstants.PLUGIN_ID, -1, "Failed to import projects", null);
151+
IProject[] alreadyExistingProjects = ProjectsManager.getWorkspaceRoot().getProjects();
152+
Instant importSessionStart = Instant.now();
148153
for (IPath rootPath : rootPaths) {
149154
File rootFolder = rootPath.toFile();
150155
try {
@@ -163,6 +168,18 @@ protected void importProjects(Collection<IPath> rootPaths, IProgressMonitor moni
163168
JavaLanguageServerPlugin.logException("Failed to import projects", e);
164169
}
165170
}
171+
if (!generatesMetadataFilesAtProjectRoot()) {
172+
Set<IProject> newProjects = new HashSet<>(Arrays.asList(getWorkspaceRoot().getProjects()));
173+
newProjects.removeAll(Arrays.asList(alreadyExistingProjects));
174+
for (IProject project : newProjects) {
175+
try {
176+
LinkResourceUtil.linkMetadataResourcesIfNewer(project, importSessionStart);
177+
} catch (CoreException e) {
178+
importStatusCollection.add(e.getStatus());
179+
JavaLanguageServerPlugin.logException("Failed to import projects", e);
180+
}
181+
}
182+
}
166183
if (!importStatusCollection.isOK()) {
167184
throw new CoreException(importStatusCollection);
168185
}
@@ -349,10 +366,29 @@ public static IProject createJavaProject(IProject project, IProgressMonitor moni
349366
return createJavaProject(project, null, "src", "bin", monitor);
350367
}
351368

369+
/*
370+
* ⚠ These value is duplicated in ProjectsManager as both bundles must remain independent,
371+
* but the same dir should be used for .project or .settings/.classpath.
372+
* So when updating one, think about updating the other.
373+
*/
374+
public static final String GENERATES_METADATA_FILES_AT_PROJECT_ROOT = "java.import.generatesMetadataFilesAtProjectRoot";
375+
376+
/**
377+
* Check whether the metadata files needs to be generated at project root.
378+
*/
379+
public static boolean generatesMetadataFilesAtProjectRoot() {
380+
String property = System.getProperty(GENERATES_METADATA_FILES_AT_PROJECT_ROOT);
381+
if (property == null) {
382+
return true;
383+
}
384+
return Boolean.parseBoolean(property);
385+
}
386+
352387
public static IProject createJavaProject(IProject project, IPath projectLocation, String src, String bin, IProgressMonitor monitor) throws CoreException, OperationCanceledException {
353388
if (project.exists()) {
354389
return project;
355390
}
391+
Instant creationRequestInstant = Instant.now();
356392
JavaLanguageServerPlugin.logInfo("Creating the Java project " + project.getName());
357393
//Create project
358394
IProjectDescription description = ResourcesPlugin.getWorkspace().newProjectDescription(project.getName());
@@ -400,6 +436,10 @@ public static IProject createJavaProject(IProject project, IPath projectLocation
400436
//Add JVM to project class path
401437
javaProject.setRawClasspath(classpaths.toArray(new IClasspathEntry[0]), monitor);
402438

439+
if (!generatesMetadataFilesAtProjectRoot()) {
440+
LinkResourceUtil.linkMetadataResourcesIfNewer(project, creationRequestInstant);
441+
}
442+
403443
JavaLanguageServerPlugin.logInfo("Finished creating the Java project " + project.getName());
404444
return project;
405445
}
@@ -434,6 +474,7 @@ public IStatus runInWorkspace(IProgressMonitor monitor) {
434474
updateEncoding(monitor);
435475
project.deleteMarkers(BUILD_FILE_MARKER_TYPE, false, IResource.DEPTH_ONE);
436476
long elapsed = System.currentTimeMillis() - start;
477+
replaceLinkedMetadataWithLocal(project);
437478
JavaLanguageServerPlugin.logInfo("Updated " + projectName + " in " + elapsed + " ms");
438479
} catch (Exception e) {
439480
String msg = "Error updating " + projectName;
@@ -641,6 +682,13 @@ public void reportProjectsStatus() {
641682
JavaLanguageServerPlugin.sendStatus(ServiceStatus.ProjectStatus, "OK");
642683
}
643684
}
685+
686+
private void replaceLinkedMetadataWithLocal(IProject p) throws CoreException {
687+
if (new File(p.getLocation().toFile(), IJavaProject.CLASSPATH_FILE_NAME).exists() &&
688+
p.getFile(IJavaProject.CLASSPATH_FILE_NAME).isLinked()) {
689+
p.getFile(IJavaProject.CLASSPATH_FILE_NAME).delete(false, false, null);
690+
}
691+
}
644692

645693
class UpdateProjectsWorkspaceJob extends WorkspaceJob {
646694

org.eclipse.jdt.ls.filesystem/.classpath

Lines changed: 0 additions & 7 deletions
This file was deleted.

org.eclipse.jdt.ls.filesystem/.project

Lines changed: 0 additions & 45 deletions
This file was deleted.

org.eclipse.jdt.ls.filesystem/.settings/org.eclipse.core.resources.prefs

Lines changed: 0 additions & 2 deletions
This file was deleted.

org.eclipse.jdt.ls.filesystem/.settings/org.eclipse.jdt.core.prefs

Lines changed: 0 additions & 9 deletions
This file was deleted.

org.eclipse.jdt.ls.filesystem/.settings/org.eclipse.m2e.core.prefs

Lines changed: 0 additions & 4 deletions
This file was deleted.

org.eclipse.jdt.ls.filesystem/META-INF/MANIFEST.MF

Lines changed: 0 additions & 16 deletions
This file was deleted.

org.eclipse.jdt.ls.filesystem/build.properties

Lines changed: 0 additions & 6 deletions
This file was deleted.

org.eclipse.jdt.ls.filesystem/plugin.properties

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)