Skip to content

Commit 27f6734

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 1813177 commit 27f6734

File tree

25 files changed

+238
-594
lines changed

25 files changed

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

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

Lines changed: 47 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,6 +366,24 @@ 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;
@@ -361,6 +396,9 @@ public static IProject createJavaProject(IProject project, IPath projectLocation
361396
}
362397
project.create(description, monitor);
363398
project.open(monitor);
399+
if (!generatesMetadataFilesAtProjectRoot()) {
400+
LinkResourceUtil.linkMetadataResourcesIfNewer(project, Instant.MIN);
401+
}
364402

365403
//Turn into Java project
366404
description = project.getDescription();
@@ -434,6 +472,7 @@ public IStatus runInWorkspace(IProgressMonitor monitor) {
434472
updateEncoding(monitor);
435473
project.deleteMarkers(BUILD_FILE_MARKER_TYPE, false, IResource.DEPTH_ONE);
436474
long elapsed = System.currentTimeMillis() - start;
475+
replaceLinkedMetadataWithLocal(project);
437476
JavaLanguageServerPlugin.logInfo("Updated " + projectName + " in " + elapsed + " ms");
438477
} catch (Exception e) {
439478
String msg = "Error updating " + projectName;
@@ -641,6 +680,13 @@ public void reportProjectsStatus() {
641680
JavaLanguageServerPlugin.sendStatus(ServiceStatus.ProjectStatus, "OK");
642681
}
643682
}
683+
684+
private void replaceLinkedMetadataWithLocal(IProject p) throws CoreException {
685+
if (new File(p.getLocation().toFile(), IJavaProject.CLASSPATH_FILE_NAME).exists() &&
686+
p.getFile(IJavaProject.CLASSPATH_FILE_NAME).isLinked()) {
687+
p.getFile(IJavaProject.CLASSPATH_FILE_NAME).delete(false, false, null);
688+
}
689+
}
644690

645691
class UpdateProjectsWorkspaceJob extends WorkspaceJob {
646692

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)