Java REPL support for JDK9 (project Kulla, JShell) (IDEA-161611)

This commit is contained in:
Eugene Zhuravlev
2017-07-04 20:11:12 +02:00
parent 7b4c9e0b8b
commit 1cf632c195
46 changed files with 3008 additions and 7 deletions

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/testStrc" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
</component>
</module>

View File

@@ -0,0 +1,200 @@
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;
/**
* @author Eugene Zhuravlev
* Date: 14-Jun-17
*/
@XmlType
public class CodeSnippet {
@XmlEnum
public enum Status {
VALID(true, true),
RECOVERABLE_DEFINED(true, true),
RECOVERABLE_NOT_DEFINED(true, false),
DROPPED(false, false),
OVERWRITTEN(false, false),
REJECTED(false, false),
NONEXISTENT(false, false),
UNKNOWN (false, false);
private final boolean myIsActive;
private final boolean myIsDefined;
Status(boolean isActive, boolean isDefined) {
this.myIsActive = isActive;
this.myIsDefined = isDefined;
}
public boolean isActive() {
return myIsActive;
}
public boolean isDefined() {
return myIsDefined;
}
}
@XmlEnum
public enum Kind {
IMPORT(true),
TYPE_DECL(true),
METHOD(true),
VAR(true),
EXPRESSION(false),
STATEMENT(false),
ERRONEOUS(false),
UNKNOWN(false);
private final boolean isPersistent;
Kind(boolean isPersistent) {
this.isPersistent = isPersistent;
}
public boolean isPersistent() {
return isPersistent;
}
}
@XmlEnum
public enum SubKind {
SINGLE_TYPE_IMPORT_SUBKIND(Kind.IMPORT),
TYPE_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
SINGLE_STATIC_IMPORT_SUBKIND(Kind.IMPORT),
STATIC_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
CLASS_SUBKIND(Kind.TYPE_DECL),
INTERFACE_SUBKIND(Kind.TYPE_DECL),
ENUM_SUBKIND(Kind.TYPE_DECL),
ANNOTATION_TYPE_SUBKIND(Kind.TYPE_DECL),
METHOD_SUBKIND(Kind.METHOD),
VAR_DECLARATION_SUBKIND(Kind.VAR),
VAR_DECLARATION_WITH_INITIALIZER_SUBKIND(Kind.VAR, true, true),
TEMP_VAR_EXPRESSION_SUBKIND(Kind.VAR, true, true),
VAR_VALUE_SUBKIND(Kind.EXPRESSION, true, true),
ASSIGNMENT_SUBKIND(Kind.EXPRESSION, true, true),
OTHER_EXPRESSION_SUBKIND(Kind.EXPRESSION, true, true),
STATEMENT_SUBKIND(Kind.STATEMENT, true, false),
UNKNOWN_SUBKIND(Kind.ERRONEOUS, false, false);
private final boolean isExecutable;
private final boolean hasValue;
private final Kind kind;
SubKind(Kind kind) {
this.kind = kind;
this.isExecutable = false;
this.hasValue = false;
}
SubKind(Kind kind, boolean isExecutable, boolean hasValue) {
this.kind = kind;
this.isExecutable = isExecutable;
this.hasValue = hasValue;
}
public boolean isExecutable() {
return isExecutable;
}
public boolean hasValue() {
return hasValue;
}
public Kind kind() {
return kind;
}
}
private String myId;
private Kind myKind;
private SubKind mySubKind;
private String myCodeText;
private String myPresentation;
public CodeSnippet() {
}
public CodeSnippet(String id, Kind kind, SubKind subKind, String codeText, String presentation) {
myId = id;
myKind = kind;
mySubKind = subKind;
myCodeText = codeText;
myPresentation = presentation;
}
public String getId() {
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;
if (o == null || getClass() != o.getClass()) return false;
CodeSnippet snippet = (CodeSnippet)o;
if (myId != null ? !myId.equals(snippet.myId) : snippet.myId != null) 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;
return true;
}
@Override
public int hashCode() {
int result = myId != null ? myId.hashCode() : 0;
result = 31 * result + (myKind != null ? myKind.hashCode() : 0);
result = 31 * result + (mySubKind != null ? mySubKind.hashCode() : 0);
result = 31 * result + (myCodeText != null ? myCodeText.hashCode() : 0);
result = 31 * result + (myPresentation != null ? myPresentation.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,10 @@
package com.intellij.execution.jshell.protocol;
/**
* @author Eugene Zhuravlev
* Date: 12-Jun-17
*/
public class Endpoint {
public static final String MSG_BEGIN = "__#begin#__";
public static final String MSG_END = "__#end#__";
}

View File

@@ -0,0 +1,97 @@
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @author Eugene Zhuravlev
* Date: 12-Jun-17
*/
@XmlRootElement
public class Event {
private CodeSnippet myCauseSnippet;
private CodeSnippet mySnippet;
private CodeSnippet.Status myPreviousStatus;
private CodeSnippet.Status myStatus;
private String myValue;
private String myExceptionText;
private String myDiagnostic;
public Event() {
}
public Event(CodeSnippet snippet, CodeSnippet causeSnippet,
CodeSnippet.Status status, CodeSnippet.Status previousStatus,
String value) {
myCauseSnippet = causeSnippet;
mySnippet = snippet;
myPreviousStatus = previousStatus;
myStatus = status;
myValue = value;
}
public CodeSnippet.Status getPreviousStatus() {
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;
}
public String getDiagnostic() {
return myDiagnostic;
}
@XmlElement
public void setDiagnostic(String diagnostic) {
myDiagnostic = diagnostic;
}
}

View File

@@ -0,0 +1,27 @@
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.annotation.XmlAttribute;
/**
* @author Eugene Zhuravlev
* Date: 12-Jun-17
*/
public abstract class Message {
private String myUid;
public Message() {
}
public Message(String uid) {
myUid = uid;
}
public String getUid() {
return myUid;
}
@XmlAttribute
public void setUid(String uid) {
myUid = uid;
}
}

View File

@@ -0,0 +1,46 @@
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.*;
import java.util.function.Consumer;
/**
* @author Eugene Zhuravlev
* Date: 12-Jun-17
*/
public class MessageReader<T> extends Endpoint {
private final BufferedReader myIn;
private final JAXBContext myContext;
public MessageReader(InputStream input, Class<T> msgType) throws Exception {
myIn = new BufferedReader(new InputStreamReader(input));
myContext = JAXBContext.newInstance(msgType);
}
public T receive(final Consumer<String> unparsedOutputSink) throws IOException {
while (true) {
String line = myIn.readLine();
if (line == null) {
return null;
}
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");
}
try {
//noinspection unchecked
return (T)myContext.createUnmarshaller().unmarshal(new StringReader(buf.toString()));
}
catch (JAXBException e) {
throw new IOException(e);
}
}
else {
unparsedOutputSink.accept(line + "\n");
}
}
}
}

View File

@@ -0,0 +1,41 @@
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;
/**
* @author Eugene Zhuravlev
* Date: 12-Jun-17
*/
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 {
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();
}
}
}

View File

@@ -0,0 +1,47 @@
package com.intellij.execution.jshell.protocol;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @author Eugene Zhuravlev
* Date: 12-Jun-17
*/
@XmlRootElement
public class Request extends Message{
private Command myCommand;
private String myCodeText;
@XmlEnum
public enum Command{
EVAL, DROP_STATE, EXIT
}
public Request() {
}
public Request(String uid, Command cmd, String codeText) {
super(uid);
myCommand = cmd;
myCodeText = codeText;
}
public Command getCommand() {
return myCommand;
}
@XmlElement
public void setCommand(Command command) {
myCommand = command;
}
public String getCodeText() {
return myCodeText;
}
@XmlElement
public void setCodeText(String codeText) {
myCodeText = codeText;
}
}

View File

@@ -0,0 +1,44 @@
package com.intellij.execution.jshell.protocol;
import com.sun.xml.internal.txw2.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Eugene Zhuravlev
* Date: 12-Jun-17
*/
@XmlRootElement
public class Response extends Message{
private List<Event> myEvents;
public Response() {
}
public Response(String uid, Event... events) {
super(uid);
Collections.addAll(myEvents = new ArrayList<>(), events);
}
@XmlElement
public List<Event> getEvents() {
return myEvents;
}
@XmlElement
public void setEvents(List<Event> events) {
myEvents = events;
}
public void addEvent(Event event) {
List<Event> events = myEvents;
if (events == null) {
events = new ArrayList<>();
myEvents = events;
}
events.add(event);
}
}

View File

@@ -0,0 +1,61 @@
package com.intellij.execution.jshell.protocol;
import junit.framework.TestCase;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.UUID;
/**
* @author Eugene Zhuravlev
* Date: 12-Jun-17
*/
public class JShellMessageMarshallingTest extends TestCase {
private static final Event[] EMPTY_EVENT_ARRAY = new Event[0];
public void testSendReceive() throws Exception {
final PipedInputStream clientIn = new PipedInputStream();
final PipedOutputStream serverOut = new PipedOutputStream(clientIn);
final PipedInputStream serverIn = new PipedInputStream();
final PipedOutputStream clientOut = new PipedOutputStream(serverIn);
final MessageWriter<Request> clientWriter = new MessageWriter<>(clientOut, Request.class);
final MessageReader<Request> serverReader = new MessageReader<>(serverIn, Request.class);
final MessageWriter<Response> serverWriter = new MessageWriter<>(serverOut, Response.class);
final MessageReader<Response> clientReader = new MessageReader<>(clientIn, Response.class);
final Request request = new Request(UUID.randomUUID().toString(), Request.Command.EVAL,
"System.out.println(\"Hello, World!\");\n int var = 7 + 7;");
clientWriter.send(request);
final Request receivedRequest = serverReader.receive(s -> {});
assertEquals(request.getUid(), receivedRequest.getUid());
assertEquals(request.getCodeText(), receivedRequest.getCodeText());
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");
final Event event2 = new Event(snippet, null, CodeSnippet.Status.VALID, CodeSnippet.Status.NONEXISTENT, "14");
final Response response = new Response(request.getUid(), event1, event2);
serverWriter.send(response);
final Response receivedResponse = clientReader.receive(s -> {});
assertEquals(response.getUid(), receivedResponse.getUid());
final Event[] events = response.getEvents().toArray(EMPTY_EVENT_ARRAY);
final Event[] receivedEvents = receivedResponse.getEvents().toArray(EMPTY_EVENT_ARRAY);
assertEquals(events.length, receivedEvents.length);
for (int i = 0; i < events.length; i++) {
final Event expectedEvent = events[i];
final Event receivedEvent = receivedEvents[i];
assertEquals(expectedEvent.getSnippet(), receivedEvent.getSnippet());
assertEquals(expectedEvent.getCauseSnippet(), receivedEvent.getCauseSnippet());
assertEquals(expectedEvent.getStatus(), receivedEvent.getStatus());
assertEquals(expectedEvent.getPreviousStatus(), receivedEvent.getPreviousStatus());
assertEquals(expectedEvent.getValue(), receivedEvent.getValue());
assertEquals(expectedEvent.getExceptionText(), receivedEvent.getExceptionText());
}
}
}