Skip to content

Commit

Permalink
Avoid temporary String object creation in StreamMessageProducer
Browse files Browse the repository at this point in the history
  • Loading branch information
sebthom authored and pisv committed Mar 2, 2024
1 parent 1e19451 commit 37db32c
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 19 deletions.
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;
}
}

0 comments on commit 37db32c

Please sign in to comment.