Skip to content

Commit

Permalink
Various improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
jlahoda committed May 12, 2024
1 parent 8286650 commit 1e3d922
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,14 @@ public Task startReceiver() {
E kind = Enum.valueOf(messageTypeClass, kindName);
Handler<?> handler = messageType2Handler.get(kind);
Object dataValue = Utils.gson.fromJson(new String(dataBytes, StandardCharsets.UTF_8), handler.messageTypeDataClass);
handler.run(dataValue).handle((result, exception) -> {
CompletableFuture<?> future;
try {
future = handler.run(dataValue);
} catch (Throwable t) {
future = new CompletableFuture<>();
future.completeExceptionally(t);
}
future.handle((result, exception) -> {
byte tagChar;
byte[] messageBytes;
if (exception == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,20 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.io.UncheckedIOException;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.modules.remote.AsynchronousConnection.ReceiverBuilder;
import org.netbeans.modules.remote.RemoteInvocation;
import org.netbeans.modules.remote.Service;
import org.netbeans.modules.remote.StreamMultiplexor;
import org.netbeans.modules.remote.Streams;
import org.netbeans.modules.remote.Utils;
import org.netbeans.modules.remote.ide.lsp.LSPHandler.GetServerRequest;
import org.netbeans.modules.remote.ide.lsp.LSPHandler.GetServerResponse;
import org.netbeans.modules.remote.ide.lsp.LSPHandler.Task;
import org.netbeans.modules.remote.ide.lsp.LSPHandlerWrapper.LSPHandler;
import org.netbeans.modules.remote.ide.lsp.LSPHandlerWrapper.ServerDescription;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.lookup.ServiceProvider;

Expand All @@ -54,40 +53,46 @@ public String name() {
public void run(InputStream in, OutputStream out) {
StreamMultiplexor mainConnection = new StreamMultiplexor(in, out);
Streams controlChannels = mainConnection.getStreamsForChannel(0);
AtomicInteger nextChannel = new AtomicInteger(2);
AtomicInteger nextLanguageServer = new AtomicInteger(2);
AtomicBoolean seenJavaServer = new AtomicBoolean();
AtomicInteger nextChannel = new AtomicInteger(2);

RemoteInvocation.receiver(controlChannels.in(), controlChannels.out(), new LSPHandler() {
private final Map<Project, ServerDescription> project2JavaDescriptions = new WeakHashMap<>();
@Override
public ServerDescription startServer(String projectPath, String mimePath) {
try {
FileObject prjDir = Utils.resolveLocalPath(projectPath);
Project prj = prjDir != null ? ProjectManager.getDefault().findProject(prjDir) : null;

new ReceiverBuilder<Task>(controlChannels.in(), controlChannels.out(), Task.class)
.addHandler(Task.GET_SERVER, GetServerRequest.class, (p) -> {
CompletableFuture<Object> result = new CompletableFuture<>();
if (prj == null) {
return null;
}

try {
FileObject prjDir = Utils.resolveLocalPath(p.projectPath);
Project prj = prjDir != null ? ProjectManager.getDefault().findProject(prjDir) : null;
if ("text/x-java".equals(mimePath)) {
return project2JavaDescriptions.computeIfAbsent(prj, p -> {
try {
int serverNumber = nextLanguageServer.incrementAndGet();
int channelNumber = nextChannel.incrementAndGet();
Streams javaChannel = mainConnection.getStreamsForChannel(channelNumber);

if (prj != null) {
if (!seenJavaServer.get()) {
Streams javaChannel = mainConnection.getStreamsForChannel(2);
Lookup.getDefault().lookup(StartJavaServerHack.class).start(javaChannel.in(), javaChannel.out());
seenJavaServer.set(true);
Lookup.getDefault().lookup(StartJavaServerHack.class).start(javaChannel.in(), javaChannel.out());
return new ServerDescription(serverNumber, channelNumber);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
});
}
result.complete(new GetServerResponse(2, 2));
return null;
// for (LanguageServerProvider provider : Lookup.getDefault().lookupAll(LanguageServerProvider.class)) {
// //TODO: multiple servers!
// provider.startServer(Lookups.fixed(prj));
// }
// result.complete(new LoadProjectResponse(isProject));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
// for (LanguageServerProvider provider : Lookup.getDefault().lookupAll(LanguageServerProvider.class)) {
// //TODO: multiple servers!
// provider.startServer(Lookups.fixed(prj));
// }
// result.complete(new LoadProjectResponse(isProject));
} catch (IOException ex) {
ex.printStackTrace();
Exceptions.printStackTrace(ex);
result.completeExceptionally(ex);
}

return result;
}).startReceiver();
});
}

public interface StartJavaServerHack {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public synchronized Remote getRemote(RemoteDescription remoteDescription) {

connectionOptions.add("ssh");
connectionOptions.addAll(Arrays.asList(remoteDescription.connectionString().split(" +")));
connectionOptions.add(remoteDescription.installDir + "/bin/netbeans");
connectionOptions.add(remoteDescription.installDir + "/bin/netbeans.remote");
connectionOptions.add("--start-remote-agent=shutdown");
connectionOptions.add("--nogui");
connectionOptions.add("--userdir");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,26 @@ public URLStreamHandler createURLStreamHandler(String protocol) {
return new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
FileObject file = URLMapper.findFileObject(u);

if (file == null) {
return null;
}

return new URLConnection(u) {
@Override
public void connect() throws IOException {
this.connected = true;
}
@Override
public InputStream getInputStream() throws IOException {
return file.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
return file.getOutputStream();
}
};
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,82 +18,67 @@
*/
package org.netbeans.modules.remote.ide.lsp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.netbeans.api.project.Project;
import org.netbeans.modules.lsp.client.spi.LanguageServerProvider.LanguageServerDescription;
import org.netbeans.modules.remote.AsynchronousConnection.Sender;
import org.netbeans.modules.remote.RemoteInvocation;
import org.netbeans.modules.remote.StreamMultiplexor;
import org.netbeans.modules.remote.Streams;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;

/**
*
*/
public class LSPHandler {
public class LSPHandlerWrapper {
private final StreamMultiplexor mainConnection;
private final Sender connection;
private final LSPHandler handler;
private final Map<Integer, LanguageServerDescription> serverId2Description = new HashMap<>();

public LSPHandler(InputStream in, OutputStream out) {
public LSPHandlerWrapper(InputStream in, OutputStream out) {
this.mainConnection = new StreamMultiplexor(in, out);
Streams controlChannels = mainConnection.getStreamsForChannel(0);
this.connection = new Sender(controlChannels.in(), controlChannels.out());
this.handler = RemoteInvocation.caller(controlChannels.in(), controlChannels.out(), LSPHandler.class);
}

public LanguageServerDescription getServer(Lookup context) {
public LanguageServerDescription getServer(Lookup context, String mimePath) {
Project prj = context.lookup(Project.class);

if (prj == null) {
return null;
}

try {
String relativePath = "/" + prj.getProjectDirectory().getPath();
GetServerResponse response = connection.sendAndReceive(Task.GET_SERVER, new GetServerRequest(relativePath), GetServerResponse.class).get();
return serverId2Description.computeIfAbsent(response.serverId, id -> {
Streams streams = mainConnection.getStreamsForChannel(response.dataChannelId);
return LanguageServerDescription.create(streams.in(), streams.out(), null);
});
} catch (IOException | InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
String projectPath = "/" + prj.getProjectDirectory().getPath();
ServerDescription serverDescription = handler.startServer(projectPath, mimePath);

public static class GetServerRequest {
public String projectPath;

public GetServerRequest() {
if (serverDescription == null) {
return null;
}

public GetServerRequest(String projectPath) {
this.projectPath = projectPath;
}
return serverId2Description.computeIfAbsent(serverDescription.serverId, id -> {
Streams streams = mainConnection.getStreamsForChannel(serverDescription.channelId);
return LanguageServerDescription.create(streams.in(), streams.out(), null);
});
}

public interface LSPHandler {
public ServerDescription startServer(String projectPath, String mimePath);
}

public static class GetServerResponse {
public static class ServerDescription {
public int serverId;
public int dataChannelId;
public int channelId;

public GetServerResponse() {
public ServerDescription() {
}

public GetServerResponse(int serverId, int dataChannelId) {
public ServerDescription(int serverId, int channelId) {
this.serverId = serverId;
this.dataChannelId = dataChannelId;
this.channelId = channelId;
}

}

public enum Task {
GET_SERVER;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,59 @@
*/
package org.netbeans.modules.remote.ide.lsp;

import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.project.Project;
import org.netbeans.modules.lsp.client.spi.LanguageServerProvider;
import org.netbeans.modules.remote.ide.RemoteManager;
import org.netbeans.modules.remote.ide.fs.RemoteFileSystem;
import org.netbeans.spi.editor.mimelookup.MimeDataProvider;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileSystem;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ServiceProvider;

/**
*
*/
@MimeRegistration(service=LanguageServerProvider.class, mimeType="", position=0)
public class RemoteLanguageServerProvider implements LanguageServerProvider {

private final MimePath mimePath;

public RemoteLanguageServerProvider(MimePath mimePath) {
this.mimePath = mimePath;
}

@Override
public LanguageServerDescription startServer(Lookup lookup) {
try {
Project prj = lookup.lookup(Project.class);

if (prj == null) {
//cannot currently create language servers with a project:
//cannot currently create language servers without a project:
return null;
}

FileSystem fs = prj.getProjectDirectory().getFileSystem();

if (fs instanceof RemoteFileSystem) {
RemoteFileSystem rfs = (RemoteFileSystem) fs;
return RemoteManager.getDefault().getService(rfs.getRemoteDescription(), "lsp", streams -> new LSPHandler(streams.in(), streams.out())).getServer(lookup);
return RemoteManager.getDefault().getService(rfs.getRemoteDescription(), "lsp", streams -> new LSPHandlerWrapper(streams.in(), streams.out())).getServer(lookup, mimePath.getPath());
}
} catch (FileStateInvalidException ex) {
Exceptions.printStackTrace(ex);
}
return null;
}

@ServiceProvider(service=MimeDataProvider.class)
public static final class MimeDataProviderImpl implements MimeDataProvider {

@Override
public Lookup getLookup(MimePath mimePath) {
return Lookups.fixed(new RemoteLanguageServerProvider(mimePath));
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,33 @@ public void testExceptional() throws Throwable {
}
}

public void testRunFails() throws Throwable {
var server = new ServerSocket(0);
var acceptedSocket = new AtomicReference<Socket>();
var connectThread = new Thread(() -> {
try {
Socket accepted = server.accept();
acceptedSocket.set(accepted);
new ReceiverBuilder<MessageType>(accepted.getInputStream(), accepted.getOutputStream(), MessageType.class)
.addHandler(MessageType.ECHO, Integer.class, in -> {
throw new UnsupportedOperationException();
}).startReceiver();
} catch (Throwable t) {
t.printStackTrace();
}
});
connectThread.start();
var clientSocket = new Socket(server.getInetAddress(), server.getLocalPort());
var sender = new Sender(clientSocket.getInputStream(), clientSocket.getOutputStream());
CompletableFuture<Integer> result = sender.sendAndReceive(MessageType.ECHO, 1, Integer.class);
try {
result.get();
fail("Should throw an exception!");
} catch (ExecutionException ex) {
//TODO: check?
}
}

public enum MessageType {
ECHO;
}
Expand Down
Loading

0 comments on commit 1e3d922

Please sign in to comment.