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), 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.
  • Loading branch information
mickaelistria committed Jun 12, 2023
1 parent 0e04fdd commit cb10a2b
Show file tree
Hide file tree
Showing 14 changed files with 314 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand All @@ -48,6 +50,7 @@
import org.eclipse.buildship.core.internal.DefaultGradleBuild;
import org.eclipse.buildship.core.internal.preferences.PersistentModel;
import org.eclipse.buildship.core.internal.util.gradle.GradleVersion;
import org.eclipse.buildship.core.internal.workspace.WorkspaceOperations;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
Expand Down Expand Up @@ -83,6 +86,9 @@
import org.eclipse.lsp4j.MessageType;
import org.gradle.tooling.model.build.BuildEnvironment;
import org.gradle.tooling.model.build.GradleEnvironment;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;

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


public GradleProjectImporter() {
// replace the project creation in buildship to hook in usage of linked resources
BundleContext context = CorePlugin.getInstance().getBundle().getBundleContext();
ServiceReference<WorkspaceOperations> serviceReference = context.getServiceReference(WorkspaceOperations.class);
Dictionary<String, Object> props = new Hashtable<>(1, 1);
props.put(Constants.SERVICE_RANKING, 2);
context.ungetService(serviceReference);
context.registerService(WorkspaceOperations.class, new WorkspaceOperationsWithLink(), props);
}

/* (non-Javadoc)
* @see org.eclipse.jdt.ls.core.internal.managers.IProjectImporter#applies(org.eclipse.core.runtime.IProgressMonitor)
*/
Expand Down
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()) {
linkResources(project);
}

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

public static void linkResources(IProject project) throws CoreException {
{
IFolder settingsFolder = project.getFolder(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME);
if (!settingsFolder.exists()) {
File diskFolder = getMetaDataFilePath(settingsFolder.getProject().getName(), settingsFolder.getProjectRelativePath()).toFile();
diskFolder.mkdirs();
settingsFolder.createLink(diskFolder.toURI(), IResource.NONE, new NullProgressMonitor());
}
}
{
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(factorypathFile.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.NONE, new NullProgressMonitor());
factorypathFile.setContents(new ByteArrayInputStream("<factorypath/>".getBytes()), 0, null);
}
}
}

@Override
public Job updateProject(IProject project, boolean force) {
if (project == null || ProjectUtils.isInternalBuildSupport(BuildSupportManager.find(project).orElse(null))) {
Expand Down Expand Up @@ -426,6 +516,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 +716,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
@@ -0,0 +1,113 @@
/*******************************************************************************
* 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
* http://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.util.List;
import java.util.Map;

import org.eclipse.buildship.core.internal.workspace.DefaultWorkspaceOperations;
import org.eclipse.buildship.core.internal.workspace.WorkspaceOperations;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;

public class WorkspaceOperationsWithLink implements WorkspaceOperations {

private final WorkspaceOperations delegate = new DefaultWorkspaceOperations();

@Override
public ImmutableList<IProject> getAllProjects() {
return delegate.getAllProjects();
}

@Override
public Optional<IProject> findProjectByName(String name) {
return delegate.findProjectByName(name);
}

@Override
public Optional<IProject> findProjectByLocation(File location) {
return delegate.findProjectByLocation(location);
}

@Override
public Optional<IProjectDescription> findProjectDescriptor(File location, IProgressMonitor monitor) {
return delegate.findProjectDescriptor(location, monitor);
}

@Override
public IProject createProject(String name, File location, List<String> natureIds, IProgressMonitor monitor) {
IProject res = delegate.createProject(name, location, natureIds, monitor);
if (!ProjectsManager.generatesMetadataFilesAtProjectRoot()) {
try {
ProjectsManager.linkResources(res);
} catch (CoreException ex) {
JavaLanguageServerPlugin.logException(ex);
}
}
return res;
}

@Override
public IProject includeProject(IProjectDescription projectDescription, List<String> extraNatureIds, IProgressMonitor monitor) {
return delegate.includeProject(projectDescription, extraNatureIds, monitor);
}

@Override
public void refreshProject(IProject project, IProgressMonitor monitor) {
delegate.refreshProject(project, monitor);
}

@Override
public void addNature(IProject project, String natureId, IProgressMonitor monitor) {
delegate.addNature(project, natureId, monitor);
}

@Override
public void removeNature(IProject project, String natureId, IProgressMonitor monitor) {
delegate.removeNature(project, natureId, monitor);
}

@Override
public void addBuildCommand(IProject project, String name, Map<String, String> arguments, IProgressMonitor monitor) {
delegate.addBuildCommand(project, name, arguments, monitor);
}

@Override
public void removeBuildCommand(IProject project, String name, IProgressMonitor monitor) {
delegate.removeBuildCommand(project, name, monitor);
}

@Override
public void validateProjectName(String name, File location) {
delegate.validateProjectName(name, location);
}

@Override
public IProject renameProject(IProject project, String newName, IProgressMonitor monitor) {
return delegate.renameProject(project, newName, monitor);
}

@Override
public boolean isNatureRecognizedByEclipse(String natureId) {
return delegate.isNatureRecognizedByEclipse(natureId);
}

@Override
public boolean isWtpInstalled() {
return delegate.isWtpInstalled();
}
}
Loading

0 comments on commit cb10a2b

Please sign in to comment.