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

Avoid temporary String object creation in StreamMessageProducer #816

Merged
merged 1 commit into from
Mar 2, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
/******************************************************************************
* Copyright (c) 2016 TypeFox and others.
*
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
******************************************************************************/
package org.eclipse.lsp4j.jsonrpc.json;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -24,6 +26,7 @@
import org.eclipse.lsp4j.jsonrpc.MessageIssueHandler;
import org.eclipse.lsp4j.jsonrpc.MessageProducer;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.util.LimitedInputStream;

/**
* A message producer that reads from an input stream and parses messages from JSON.
Expand All @@ -43,7 +46,7 @@ public class StreamMessageProducer implements MessageProducer, Closeable, Messag
public StreamMessageProducer(InputStream input, MessageJsonHandler jsonHandler) {
this(input, jsonHandler, null);
}

public StreamMessageProducer(InputStream input, MessageJsonHandler jsonHandler, MessageIssueHandler issueHandler) {
this.input = input;
this.jsonHandler = jsonHandler;
Expand Down Expand Up @@ -133,7 +136,7 @@ protected void fireError(Throwable error) {
String message = error.getMessage() != null ? error.getMessage() : "An error occurred while processing an incoming message.";
LOG.log(Level.SEVERE, message, error);
}

/**
* Report that the stream was closed through an exception.
*/
Expand Down Expand Up @@ -169,27 +172,15 @@ protected void parseHeader(String line, Headers headers) {

/**
* Read the JSON content part of a message, parse it, and notify the callback.
*
*
* @return {@code true} if we should continue reading from the input stream, {@code false} if we should stop
*/
protected boolean handleMessage(InputStream input, Headers headers) throws IOException {
if (callback == null)
callback = message -> LOG.log(Level.INFO, "Received message: " + message);

try {
int contentLength = headers.contentLength;
byte[] buffer = new byte[contentLength];
int bytesRead = 0;

while (bytesRead < contentLength) {
int readResult = input.read(buffer, bytesRead, contentLength - bytesRead);
if (readResult == -1)
return false;
bytesRead += readResult;
}

String content = new String(buffer, headers.charset);
try {
try {
try (final Reader content = new InputStreamReader(new LimitedInputStream(input, headers.contentLength, false), headers.charset)) {
Message message = jsonHandler.parseMessage(content);
callback.consume(message);
} catch (MessageIssueException exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/******************************************************************************
* Copyright (c) 2024 Sebastian Thomschke and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
******************************************************************************/
package org.eclipse.lsp4j.jsonrpc.util;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class LimitedInputStream extends FilterInputStream {
private static final int EOF = -1;

private int bytesRemaining;
private final boolean closeWrapped;
private boolean isClosed;
private int mark = EOF;

/**
* @param closeWrapped controls if the underlying {@link InputStream} should also be closed via {@link #close()}
*/
public LimitedInputStream(final InputStream wrapped, final int maxBytesToRead, final boolean closeWrapped) {
super(wrapped);
if (maxBytesToRead < 0)
throw new IllegalArgumentException("[maxBytesToRead] must be >= 0");
bytesRemaining = maxBytesToRead;
this.closeWrapped = closeWrapped;
}

@Override
public int available() throws IOException {
if (isClosed)
return 0;
final int availableBytes = in.available();
return Math.min(availableBytes, bytesRemaining);
}

@Override
public void close() throws IOException {
if (closeWrapped) {
in.close();
}
isClosed = true;
}

@Override
public int read() throws IOException {
if (isClosed || bytesRemaining < 1)
return EOF;

final int data = in.read();
if (data != EOF) {
bytesRemaining--;
}
return data;
}

@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
if (isClosed || bytesRemaining < 1)
return EOF;

final int bytesRead = in.read(b, off, Math.min(len, bytesRemaining));
if (bytesRead != EOF) {
bytesRemaining -= bytesRead;
}
return bytesRead;
}

@Override
public synchronized void mark(final int readlimit) {
in.mark(readlimit);
mark = bytesRemaining;
}

@Override
public synchronized void reset() throws IOException {
if (!in.markSupported())
throw new IOException("mark/reset not supported");

if (mark == EOF)
throw new IOException("mark not set");

in.reset();
bytesRemaining = mark;
}

@Override
public long skip(final long n) throws IOException {
final long skipped = in.skip(Math.min(n, bytesRemaining));
bytesRemaining -= skipped;
return skipped;
}
}