Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project info exported through LSP #4595

Merged
merged 1 commit into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.lsp.server.project;

import java.net.URI;

/**
*
* @author sdedic
*/
public class LspProjectInfo {
/**
* Project's directory
*/
public URI projectDirectory;

/**
* Project's name.
*/
public String name;

/**
* Project's display name as defined in project file(s)
*/
public String displayName;

/**
* The build system / project type. Ant, Gradle, Maven.
*/
public String projectType;

/**
* URIs of subprojects. Usually children of the project's own directory.
*/
public URI[] subprojects;

/**
* If part of a reactor or multi-project, the URI of the root project.
*/
public URI rootProject;

/**
* Supported project actions. Names.
*/
public String[] projectActionNames;
}
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,9 @@ private InitializeResult constructInitResponse(InitializeParams init, JavaSource
JAVA_SUPER_IMPLEMENTATION,
JAVA_SOURCE_FOR,
JAVA_CLEAR_PROJECT_CACHES,
NATIVE_IMAGE_FIND_DEBUG_PROCESS_TO_ATTACH));
NATIVE_IMAGE_FIND_DEBUG_PROCESS_TO_ATTACH,
JAVA_PROJECT_INFO
));
for (CodeActionsProvider codeActionsProvider : Lookup.getDefault().lookupAll(CodeActionsProvider.class)) {
commands.addAll(codeActionsProvider.getCommands());
}
Expand Down Expand Up @@ -944,6 +946,12 @@ protected LanguageClient client() {
* new project files were generated into workspace subtree.
*/
public static final String JAVA_CLEAR_PROJECT_CACHES = "java.clear.project.caches";

/**
* For a project directory, returns basic project information and structure.
* Syntax: nbls.project.info(locations : String | String[], options? : { projectStructure? : boolean; actions? : boolean; recursive? : boolean }) : LspProjectInfo
*/
public static final String JAVA_PROJECT_INFO = "nbls.project.info";

static final String INDEXING_COMPLETED = "Indexing completed.";
static final String NO_JAVA_SUPPORT = "Cannot initialize Java support on JDK ";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.netbeans.modules.java.lsp.server.protocol;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
Expand All @@ -29,6 +30,7 @@
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
Expand All @@ -48,6 +50,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -99,6 +102,7 @@
import org.netbeans.api.java.source.ui.ElementOpen;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
Expand All @@ -111,6 +115,7 @@
import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.java.lsp.server.debugging.attach.AttachConfigurations;
import org.netbeans.modules.java.lsp.server.debugging.attach.AttachNativeConfigurations;
import org.netbeans.modules.java.lsp.server.project.LspProjectInfo;
import org.netbeans.modules.java.source.ElementHandleAccessor;
import org.netbeans.modules.java.source.ui.JavaSymbolProvider;
import org.netbeans.modules.java.source.ui.JavaTypeProvider;
Expand Down Expand Up @@ -139,8 +144,11 @@
* @author lahvac
*/
public final class WorkspaceServiceImpl implements WorkspaceService, LanguageClientAware {

private static final Logger LOG = Logger.getLogger(WorkspaceServiceImpl.class.getName());

private static final RequestProcessor WORKER = new RequestProcessor(WorkspaceServiceImpl.class.getName(), 1, false, false);
private static final RequestProcessor PROJECT_WORKER = new RequestProcessor(WorkspaceServiceImpl.class.getName(), 5, false, false);

private final Gson gson = new Gson();
private final LspServerState server;
Expand Down Expand Up @@ -578,6 +586,56 @@ public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
}
return (CompletableFuture<Object>) (CompletableFuture<?>)result;
}

case Server.JAVA_PROJECT_INFO: {
final CompletableFuture<Object> result = new CompletableFuture<>();
List<Object> arguments = params.getArguments();
if (arguments.size() < 1) {
result.completeExceptionally(new IllegalArgumentException("Expecting URL or URL[] as an argument to " + command));
return result;
}
Object o = arguments.get(0);
URL[] locations = null;
if (o instanceof JsonArray) {
List<URL> locs = new ArrayList<>();
JsonArray a = (JsonArray)o;
a.forEach((e) -> {
if (e instanceof JsonPrimitive) {
String s = ((JsonPrimitive)e).getAsString();
try {
locs.add(new URL(s));
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Illegal location: " + s);
}
}
});
} else if (o instanceof JsonPrimitive) {
String s = ((JsonPrimitive)o).getAsString();
try {
locations = new URL[] { new URL(s) };
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Illegal location: " + s);
}
}
if (locations == null || locations.length == 0) {
result.completeExceptionally(new IllegalArgumentException("Expecting URL or URL[] as an argument to " + command));
return result;
}
boolean projectStructure = false;
boolean actions = false;
boolean recursive = false;

if (arguments.size() > 1) {
Object a2 = arguments.get(1);
if (a2 instanceof JsonObject) {
JsonObject options = (JsonObject)a2;
projectStructure = getOption(options, "projectStructure", false); // NOI18N
actions = getOption(options, "actions", false); // NOI18N
recursive = getOption(options, "recursive", false); // NOI18N
}
}
return (CompletableFuture<Object>)(CompletableFuture<?>)new ProjectInfoWorker(locations, projectStructure, recursive, actions).process();
}
default:
for (CodeActionsProvider codeActionsProvider : Lookup.getDefault().lookupAll(CodeActionsProvider.class)) {
if (codeActionsProvider.getCommands().contains(command)) {
Expand All @@ -588,6 +646,133 @@ public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
throw new UnsupportedOperationException("Command not supported: " + params.getCommand());
}

private class ProjectInfoWorker {
final URL[] locations;
final boolean projectStructure;
final boolean recursive;
final boolean actions;

Map<FileObject, LspProjectInfo> infos = new HashMap<>();
Set<Project> toOpen = new HashSet<>();

public ProjectInfoWorker(URL[] locations, boolean projectStructure, boolean recursive, boolean actions) {
this.locations = locations;
this.projectStructure = projectStructure;
this.recursive = recursive;
this.actions = actions;
}

public CompletableFuture<LspProjectInfo[]> process() {
List<FileObject> files = new ArrayList();
for (URL u : locations) {
FileObject f = URLMapper.findFileObject(u);
if (f != null) {
files.add(f);
}
}
return server.asyncOpenSelectedProjects(files, false).thenCompose(this::processProjects);
}

LspProjectInfo fillProjectInfo(Project p) {
LspProjectInfo info = infos.get(p.getProjectDirectory());
if (info != null) {
return info;
}
info = new LspProjectInfo();

ProjectInformation pi = ProjectUtils.getInformation(p);
URL projectURL = URLMapper.findURL(p.getProjectDirectory(), URLMapper.EXTERNAL);
if (projectURL != null) {
try {
info.projectDirectory = projectURL.toURI();
} catch (URISyntaxException ex) {
// should not happen
}
}
info.name = pi.getName();
info.displayName = pi.getDisplayName();

// attempt to determine the project type
ProjectManager.Result r = ProjectManager.getDefault().isProject2(p.getProjectDirectory());
info.projectType = r.getProjectType();

if (actions) {
ActionProvider ap = p.getLookup().lookup(ActionProvider.class);
if (ap != null) {
info.projectActionNames = ap.getSupportedActions();
}
}

if (projectStructure) {
Set<Project> children = ProjectUtils.getContainedProjects(p, false);
List<URI> subprojectDirs = new ArrayList<>();
for (Project c : children) {
try {
subprojectDirs.add(URLMapper.findURL(c.getProjectDirectory(), URLMapper.EXTERNAL).toURI());
} catch (URISyntaxException ex) {
// should not happen
}
}
info.subprojects = subprojectDirs.toArray(new URI[subprojectDirs.size()]);
Project root = ProjectUtils.rootOf(p);
if (root != null) {
try {
info.rootProject = URLMapper.findURL(root.getProjectDirectory(), URLMapper.EXTERNAL).toURI();
} catch (URISyntaxException ex) {
// should not happen
}
}
if (recursive) {
toOpen.addAll(children);
}
}
infos.put(p.getProjectDirectory(), info);
return info;
}

CompletableFuture<LspProjectInfo[]> processProjects(Project[] prjs) {
for (Project p : prjs) {
fillProjectInfo(p);
}
if (toOpen.isEmpty()) {
return finalizeInfos();
}
List<FileObject> dirs = new ArrayList<>(toOpen.size());
for (Project p : toOpen) {
dirs.add(p.getProjectDirectory());
}
toOpen.clear();
return server.asyncOpenSelectedProjects(dirs).thenCompose(this::processProjects);
}

CompletableFuture<LspProjectInfo[]> finalizeInfos() {
List<LspProjectInfo> list = new ArrayList();
for (URL u : locations) {
FileObject f = URLMapper.findFileObject(u);
Project owner = FileOwnerQuery.getOwner(f);
if (owner != null) {
list.add(infos.remove(owner.getProjectDirectory()));
} else {
list.add(null);
}
}
list.addAll(infos.values());
LspProjectInfo[] toArray = list.toArray(new LspProjectInfo[list.size()]);
return CompletableFuture.completedFuture(toArray);
}
}

private static boolean getOption(JsonObject opts, String member, boolean def) {
if (!opts.has(member)) {
return def;
}
Object o = opts.get(member);
if (!(o instanceof JsonPrimitive)) {
return false;
}
return ((JsonPrimitive)o).getAsBoolean();
}

private final AtomicReference<BiConsumer<FileObject, Collection<TestMethodController.TestMethod>>> testMethodsListener = new AtomicReference<>();
private final AtomicReference<PropertyChangeListener> openProjectsListener = new AtomicReference<>();

Expand Down