[java] migrates JShell communication from JAXB to standard serialization (IDEA-197466)

This commit is contained in:
Roman Shevchenko
2018-10-24 19:03:41 +02:00
parent 745c62ca60
commit 2e4ed634ae
13 changed files with 94 additions and 213 deletions
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell;
import com.intellij.execution.ExecutionManager;
@@ -97,7 +83,7 @@ public class JShellHandler {
final PipedInputStream is = new PipedInputStream();
final OutputStreamWriter readerSink = new OutputStreamWriter(new PipedOutputStream(is));
myMessageReader = new MessageReader<>(is, Response.class);
myMessageWriter = new MessageWriter<>(handler.getProcessInput(), Request.class);
myMessageWriter = new MessageWriter<>(handler.getProcessInput());
handler.addProcessListener(new ProcessAdapter() {
@Override
@@ -241,8 +227,6 @@ public class JShellHandler {
if (DEBUG_PORT > 0) {
cmdLine.addParameter("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + DEBUG_PORT);
}
cmdLine.addParameter("--add-modules");
cmdLine.addParameter("java.xml.bind");
final StringBuilder launchCp = new StringBuilder().append(frontEndPath);
final String protocolJar = getLibPath(Endpoint.class);
@@ -1,3 +1,4 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.frontend;
import com.intellij.execution.jshell.protocol.*;
@@ -10,25 +11,18 @@ import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
/**
* @author Eugene Zhuravlev
*
* @noinspection UseOfSystemOutOrSystemErr
*/
@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
public class Main {
private static final String ARG_CLASSPATH = "--class-path";
private static final String ARG_CLASSPATH_FILE = "--@class-path";
private static final Consumer<String> NULL_CONSUMER = s -> {};
//private static Request createTestRequest() {
// return new Request(UUID.randomUUID().toString(), Request.Command.EVAL, "int a = 77;\n" +
// "int b = a + 3");
//}
public static void main(String[] args) throws Exception {
public static void main(String[] args) {
final MessageReader<Request> reader = new MessageReader<>(new BufferedInputStream(System.in), Request.class);
final MessageWriter<Response> writer = new MessageWriter<>(new BufferedOutputStream(System.out), Response.class);
final MessageWriter<Response> writer = new MessageWriter<>(new BufferedOutputStream(System.out));
try (JShell shell = JShell.create()) {
configureJShell(args, shell);
@@ -42,9 +36,7 @@ public class Main {
return;
}
final Response response = new Response();
response.setUid(request.getUid());
final Response response = new Response(request.getUid());
try {
// first, handle eval classpath if any
final List<String> cp = request.getClassPath();
@@ -53,7 +45,7 @@ public class Main {
shell.addToClasspath(path);
}
}
if (command == Request.Command.DROP_STATE) {
shell.snippets().forEach(snippet -> exportEvents(shell, shell.drop(snippet), response));
}
@@ -211,9 +203,8 @@ public class Main {
try {
return Enum.valueOf(toEnumOfClass, from.name());
}
catch (IllegalArgumentException ignored) {
}
catch (IllegalArgumentException ignored) { }
}
return Enum.valueOf(toEnumOfClass, "UNKNOWN");
}
}
}
@@ -1,7 +1,7 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.frontend;
/**
* @author Eugene Zhuravlev
*/
public interface Marker {
}
public interface Marker { }
@@ -1,17 +1,14 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlType;
import java.io.Serializable;
import java.util.Objects;
/**
* @author Eugene Zhuravlev
*/
@XmlType
public class CodeSnippet {
@XmlEnum
public class CodeSnippet implements Serializable {
@SuppressWarnings("unused")
public enum Status {
VALID(true, true),
RECOVERABLE_DEFINED(true, true),
@@ -39,7 +36,7 @@ public class CodeSnippet {
}
}
@XmlEnum
@SuppressWarnings("unused")
public enum Kind {
IMPORT(true),
TYPE_DECL(true),
@@ -51,15 +48,17 @@ public class CodeSnippet {
UNKNOWN(false);
private final boolean isPersistent;
Kind(boolean isPersistent) {
this.isPersistent = isPersistent;
}
public boolean isPersistent() {
return isPersistent;
}
}
@XmlEnum
@SuppressWarnings("unused")
public enum SubKind {
SINGLE_TYPE_IMPORT_SUBKIND(Kind.IMPORT),
TYPE_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
@@ -108,15 +107,14 @@ public class CodeSnippet {
}
}
private String myId;
private Kind myKind;
private SubKind mySubKind;
private String myCodeText;
private String myPresentation;
public CodeSnippet() {
}
@SuppressWarnings("unused")
public CodeSnippet() { }
public CodeSnippet(String id, Kind kind, SubKind subKind, String codeText, String presentation) {
myId = id;
@@ -130,47 +128,22 @@ public class CodeSnippet {
return myId;
}
@XmlAttribute
public void setId(String id) {
myId = id;
}
public Kind getKind() {
return myKind;
}
@XmlAttribute
public void setKind(Kind kind) {
myKind = kind;
}
public SubKind getSubKind() {
return mySubKind;
}
@XmlAttribute
public void setSubKind(SubKind subKind) {
mySubKind = subKind;
}
public String getCodeText() {
return myCodeText;
}
@XmlElement
public void setCodeText(String codeText) {
myCodeText = codeText;
}
public String getPresentation() {
return myPresentation;
}
@XmlElement
public void setPresentation(String presentation) {
myPresentation = presentation;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -178,11 +151,11 @@ public class CodeSnippet {
CodeSnippet snippet = (CodeSnippet)o;
if (myId != null ? !myId.equals(snippet.myId) : snippet.myId != null) return false;
if (!Objects.equals(myId, snippet.myId)) return false;
if (myKind != snippet.myKind) return false;
if (mySubKind != snippet.mySubKind) return false;
if (myCodeText != null ? !myCodeText.equals(snippet.myCodeText) : snippet.myCodeText != null) return false;
if (myPresentation != null ? !myPresentation.equals(snippet.myPresentation) : snippet.myPresentation != null) return false;
if (!Objects.equals(myCodeText, snippet.myCodeText)) return false;
if (!Objects.equals(myPresentation, snippet.myPresentation)) return false;
return true;
}
@@ -196,4 +169,4 @@ public class CodeSnippet {
result = 31 * result + (myPresentation != null ? myPresentation.hashCode() : 0);
return result;
}
}
}
@@ -1,3 +1,4 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
/**
@@ -6,4 +7,4 @@ package com.intellij.execution.jshell.protocol;
public class Endpoint {
public static final String MSG_BEGIN = "__#begin#__";
public static final String MSG_END = "__#end#__";
}
}
@@ -1,15 +1,12 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
/**
* @author Eugene Zhuravlev
*/
@XmlRootElement
public class Event {
public class Event implements Serializable {
private CodeSnippet myCauseSnippet;
private CodeSnippet mySnippet;
private CodeSnippet.Status myPreviousStatus;
@@ -18,8 +15,8 @@ public class Event {
private String myExceptionText;
private String myDiagnostic;
public Event() {
}
@SuppressWarnings("unused")
public Event() { }
public Event(CodeSnippet snippet, CodeSnippet causeSnippet,
CodeSnippet.Status status, CodeSnippet.Status previousStatus,
@@ -35,52 +32,26 @@ public class Event {
return myPreviousStatus;
}
@XmlElement
public void setPreviousStatus(CodeSnippet.Status previousStatus) {
myPreviousStatus = previousStatus;
}
public CodeSnippet.Status getStatus() {
return myStatus;
}
@XmlElement
public void setStatus(CodeSnippet.Status status) {
myStatus = status;
}
public String getValue() {
return myValue;
}
@XmlElement
public void setValue(String value) {
myValue = value;
}
public CodeSnippet getCauseSnippet() {
return myCauseSnippet;
}
@XmlElement
public void setCauseSnippet(CodeSnippet causeSnippet) {
myCauseSnippet = causeSnippet;
}
public CodeSnippet getSnippet() {
return mySnippet;
}
@XmlElement
public void setSnippet(CodeSnippet snippet) {
mySnippet = snippet;
}
public String getExceptionText() {
return myExceptionText;
}
@XmlElement
public void setExceptionText(String exceptionText) {
myExceptionText = exceptionText;
}
@@ -88,9 +59,8 @@ public class Event {
public String getDiagnostic() {
return myDiagnostic;
}
@XmlElement
public void setDiagnostic(String diagnostic) {
myDiagnostic = diagnostic;
}
}
}
@@ -1,15 +1,15 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.annotation.XmlAttribute;
import java.io.Serializable;
/**
* @author Eugene Zhuravlev
*/
public abstract class Message {
public abstract class Message implements Serializable {
private String myUid;
public Message() {
}
public Message() { }
public Message(String uid) {
myUid = uid;
@@ -18,9 +18,4 @@ public abstract class Message {
public String getUid() {
return myUid;
}
@XmlAttribute
public void setUid(String uid) {
myUid = uid;
}
}
}
@@ -1,8 +1,8 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.*;
import java.util.Base64;
import java.util.function.Consumer;
/**
@@ -10,11 +10,11 @@ import java.util.function.Consumer;
*/
public class MessageReader<T> extends Endpoint {
private final BufferedReader myIn;
private final JAXBContext myContext;
private final Class<T> myMsgType;
public MessageReader(InputStream input, Class<T> msgType) throws Exception {
public MessageReader(InputStream input, Class<T> msgType) {
myIn = new BufferedReader(new InputStreamReader(input));
myContext = JAXBContext.newInstance(msgType);
myMsgType = msgType;
}
public T receive(final Consumer<? super String> unparsedOutputSink) throws IOException {
@@ -26,13 +26,13 @@ public class MessageReader<T> extends Endpoint {
if (MSG_BEGIN.equals(line)) {
final StringBuilder buf = new StringBuilder();
for (String body = myIn.readLine(); !MSG_END.equals(body.trim()); body = myIn.readLine()) {
buf.append(body).append("\n");
buf.append(body);
}
try {
//noinspection unchecked
return (T)myContext.createUnmarshaller().unmarshal(new StringReader(buf.toString()));
byte[] bytes = Base64.getDecoder().decode(buf.toString());
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
return myMsgType.cast(ois.readObject());
}
catch (JAXBException e) {
catch (ClassNotFoundException e) {
throw new IOException(e);
}
}
@@ -41,5 +41,4 @@ public class MessageReader<T> extends Endpoint {
}
}
}
}
}
@@ -1,40 +1,31 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.*;
import java.util.Base64;
/**
* @author Eugene Zhuravlev
*/
public class MessageWriter<T extends Message> extends Endpoint {
private final BufferedWriter myOut;
private final JAXBContext myContext;
public MessageWriter(OutputStream output, Class<T> msgType) throws Exception {
public MessageWriter(OutputStream output) {
myOut = new BufferedWriter(new OutputStreamWriter(output));
myContext = JAXBContext.newInstance(msgType);
}
public void send(T message) throws IOException {
try {
myOut.newLine();
myOut.write(MSG_BEGIN);
myOut.newLine();
myContext.createMarshaller().marshal(message, myOut);
}
catch (JAXBException e) {
throw new IOException(e);
}
finally {
myOut.newLine();
myOut.write(MSG_END);
myOut.newLine();
myOut.flush();
}
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(buffer)) { oos.writeObject(message); }
String data = Base64.getEncoder().encodeToString(buffer.toByteArray());
}
myOut.newLine();
myOut.write(MSG_BEGIN);
myOut.newLine();
myOut.write(data);
myOut.newLine();
myOut.write(MSG_END);
myOut.newLine();
myOut.flush();
}
}
@@ -1,27 +1,23 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.ArrayList;
import java.util.List;
/**
* @author Eugene Zhuravlev
*/
@XmlRootElement
public class Request extends Message{
public class Request extends Message {
private Command myCommand;
private String myCodeText;
private List<String> myClassPath;
@XmlEnum
public enum Command{
public enum Command {
EVAL, DROP_STATE, EXIT
}
public Request() {
}
@SuppressWarnings("unused")
public Request() { }
public Request(String uid, Command cmd, String codeText) {
super(uid);
@@ -33,29 +29,14 @@ public class Request extends Message{
return myCommand;
}
@XmlElement
public void setCommand(Command command) {
myCommand = command;
}
public String getCodeText() {
return myCodeText;
}
@XmlElement
public void setCodeText(String codeText) {
myCodeText = codeText;
}
public List<String> getClassPath() {
return myClassPath;
}
@XmlElement(name = "cp")
public void setClassPath(List<String> classPath) {
myClassPath = classPath;
}
public void addClasspathItem(String path) {
List<String> cp = myClassPath;
if (cp == null) {
@@ -64,4 +45,4 @@ public class Request extends Message{
}
cp.add(path);
}
}
}
@@ -1,7 +1,6 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -9,12 +8,11 @@ import java.util.List;
/**
* @author Eugene Zhuravlev
*/
@XmlRootElement
public class Response extends Message{
public class Response extends Message {
private List<Event> myEvents;
public Response() {
}
@SuppressWarnings("unused")
public Response() { }
public Response(String uid, Event... events) {
super(uid);
@@ -25,11 +23,6 @@ public class Response extends Message{
return myEvents;
}
@XmlElement
public void setEvents(List<Event> events) {
myEvents = events;
}
public void addEvent(Event event) {
List<Event> events = myEvents;
if (events == null) {
@@ -38,4 +31,4 @@ public class Response extends Message{
}
events.add(event);
}
}
}
@@ -1,29 +1,31 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.jshell.protocol;
import junit.framework.TestCase;
import org.junit.Test;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.List;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
/**
* @author Eugene Zhuravlev
*/
public class JShellMessageMarshallingTest extends TestCase {
public class JShellMessageMarshallingTest {
private static final int PIPE_SIZE = 32 << 10;
private static final Event[] EMPTY_EVENT_ARRAY = new Event[0];
@Test(timeout = 10000)
public void testSendReceive() throws Exception {
final PipedInputStream clientIn = new PipedInputStream();
final PipedInputStream clientIn = new PipedInputStream(PIPE_SIZE);
final PipedOutputStream serverOut = new PipedOutputStream(clientIn);
final PipedInputStream serverIn = new PipedInputStream();
final PipedInputStream serverIn = new PipedInputStream(PIPE_SIZE);
final PipedOutputStream clientOut = new PipedOutputStream(serverIn);
final MessageWriter<Request> clientWriter = new MessageWriter<>(clientOut, Request.class);
final MessageWriter<Request> clientWriter = new MessageWriter<>(clientOut);
final MessageReader<Request> serverReader = new MessageReader<>(serverIn, Request.class);
final MessageWriter<Response> serverWriter = new MessageWriter<>(serverOut, Response.class);
final MessageWriter<Response> serverWriter = new MessageWriter<>(serverOut);
final MessageReader<Response> clientReader = new MessageReader<>(clientIn, Response.class);
final Request request = new Request(UUID.randomUUID().toString(), Request.Command.EVAL,
@@ -39,7 +41,8 @@ public class JShellMessageMarshallingTest extends TestCase {
final List<String> receivedClasspath = receivedRequest.getClassPath();
assertEquals(requestClasspath, receivedClasspath);
final CodeSnippet snippet = new CodeSnippet("code-snippet-id", CodeSnippet.Kind.EXPRESSION, CodeSnippet.SubKind.OTHER_EXPRESSION_SUBKIND, "a+b", "expression:a+b");
final CodeSnippet snippet = new CodeSnippet("code-snippet-id", CodeSnippet.Kind.EXPRESSION, CodeSnippet.SubKind.OTHER_EXPRESSION_SUBKIND,
"a+b", "expression:a+b");
final Event event1 = new Event(null, null, CodeSnippet.Status.UNKNOWN, CodeSnippet.Status.NONEXISTENT, null);
event1.setExceptionText("some exception");
event1.setDiagnostic("error diagnostic");
@@ -64,4 +67,4 @@ public class JShellMessageMarshallingTest extends TestCase {
assertEquals(expectedEvent.getExceptionText(), receivedEvent.getExceptionText());
}
}
}
}
Binary file not shown.