Skip to content

Commit 703c590

Browse files
committed
Fix #19 Not working with Scala 2.12 processes
1 parent 4fcba1e commit 703c590

16 files changed

+149
-177
lines changed

README.md

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,61 @@
1+
# Scalive
2+
13
This tool allows you to connect a Scala REPL console to running Oracle (Sun)
24
JVM processes without any prior setup at the target process.
35

46
[![View demo video on YouTube](http://img.youtube.com/vi/h45QQ45D9P8/0.jpg)](http://www.youtube.com/watch?v=h45QQ45D9P8)
57

68
## Download
79

8-
Download and extract
10+
For Scala 2.12, download
11+
[scalive-1.7.0.zip](https://github.com/xitrum-framework/scalive/releases/download/1.7.0/scalive-1.7.0.zip).
12+
13+
For Scala 2.10 and 2.11, download
914
[scalive-1.6.zip](https://github.com/xitrum-framework/scalive/releases/download/1.6/scalive-1.6.zip),
10-
you will see:
1115

12-
```
13-
scalive-1.6/
14-
scalive
15-
scalive.cmd
16-
scalive-1.6.jar
17-
jline-2.14.2.jar
16+
Extract the ZIP file, you will see:
1817

19-
scala-library-2.10.6.jar
20-
scala-compiler-2.10.6.jar
21-
scala-reflect-2.10.6.jar
18+
```txt
19+
scalive-1.7.0/
20+
scalive
21+
scalive.bat
22+
scalive-1.7.0.jar
2223
23-
scala-library-2.11.8.jar
24-
scala-compiler-2.11.8.jar
25-
scala-reflect-2.11.8.jar
24+
scala-library-2.12.8.jar
25+
scala-compiler-2.12.8.jar
26+
scala-reflect-2.12.8.jar
2627
```
2728

2829
scala-library, scala-compiler, and scala-reflect of the correct version
29-
that your JVM process is using will be loaded, if they have not been loaded.
30+
that your JVM process is using will be loaded, if they have not been loaded yet.
3031
The REPL console needs these libraries to work.
3132

32-
For example, your process has already loaded scala-library 2.11.8 by itself,
33+
For example, your process has already loaded scala-library 2.12.8 by itself,
3334
but scala-compiler and scala-reflect haven't been loaded, Scalive will
34-
automatically load their version 2.11.8.
35+
automatically load their version 2.12.8.
3536

3637
If none of them has been loaded, i.e. your process doesn't use Scala,
3738
Scalive will load the lastest version in the directory.
3839

39-
For your convenience, Scala 2.10.6 and 2.11.8 JARs are included above.
40+
For your convenience, Scala 2.12.8 JAR files have been included above.
4041

41-
If your process is using a different Scala version, you need to manually
42+
If your process uses a different Scala version, you need to manually
4243
download the corresponding JARs from the Internet and save them in the
4344
same directory as above.
4445

4546
## Usage
4647

47-
Run the shell script `scalive` (*nix) or `scalive.cmd` (Windows).
48+
Run the shell script `scalive` (*nix) or `scalive.bat` (Windows).
4849

4950
Run without argument to see the list of running JVM process IDs on your local machine:
5051

51-
```
52+
```sh
5253
scalive
5354
```
5455

5556
Example output:
5657

57-
```
58+
```txt
5859
JVM processes:
5960
#pid Display name
6061
13821 demos.Boot
@@ -63,7 +64,7 @@ JVM processes:
6364

6465
To connect a Scala REPL console to a process:
6566

66-
```
67+
```sh
6768
scalive <process id listed above>
6869
```
6970

@@ -80,7 +81,7 @@ Scalive only automatically loads `scala-library.jar`, `scala-compiler.jar`,
8081
If you want to load additional classes in other JARs, first run these in the
8182
REPL console to load the JAR to the system class loader:
8283

83-
```
84+
```scala
8485
val cl = ClassLoader.getSystemClassLoader.asInstanceOf[java.net.URLClassLoader]
8586
val jarSearchDirs = Array("/dir/containing/the/jar")
8687
val jarPrefix = "mylib" // Will match "mylib-xxx.jar", convenient when there's version number in the file name
@@ -90,7 +91,7 @@ scalive.Classpath.findAndAddJar(cl, jarSearchDirs, jarPrefix)
9091
Now the trick is just quit the REPL console and connect it to the target process
9192
again. You will be able to use your classes in the JAR normally:
9293

93-
```
94+
```scala
9495
import mylib.foo.Bar
9596
...
9697
```
@@ -99,13 +100,13 @@ import mylib.foo.Bar
99100

100101
## How Scalive works
101102

102-
Scalive uses the [Attach API](https://blogs.oracle.com/CoreJavaTechTips/entry/the_attach_api) in Java 6
103+
Scalive uses the [Attach API](https://blogs.oracle.com/CoreJavaTechTips/entry/the_attach_api)
103104
to tell the target process to load an [agent](http://javahowto.blogspot.jp/2006/07/javaagent-option.html).
104105

105106
Inside the target progress, the agent creates a REPL interpreter and a
106107
TCP server to let the Scalive process connect and interact with the
107108
interpreter. The Scalive process acts as a TCP client. There are 2 TCP
108-
connections, one for REPL data and one for completion data.
109+
connections, one for REPL data and one for tab key completion data.
109110

110111
Similar projects:
111112

@@ -118,7 +119,7 @@ For simplicity and to avoid memory leak when you attach/detach many times,
118119
Scalive only supports processes with only the default system class loader,
119120
without additional class loaders. Usually they are standalone JVM processes,
120121
like
121-
[Play](http://www.playframework.com/) or
122-
[Xitrum](http://xitrum-framework.github.io/) in production mode.
122+
[Play](http://www.playframework.com) or
123+
[Xitrum](http://xitrum-framework.github.io) in production mode.
123124

124125
Processes with multiple class loaders like Tomcat are currently not supported.

build.sbt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
organization := "tv.cntt"
22
name := "scalive"
3-
version := "1.6"
3+
version := "1.7.0"
44

5-
scalaVersion := "2.11.8"
5+
// Scalive is a Java library, we only use SBT to build
6+
scalaVersion := "2.12.8"
67
autoScalaLibrary := false
78
crossPaths := false // Do not append Scala versions to the generated artifacts
89

910
javacOptions ++= Seq("-Xlint:deprecation")
1011

11-
// Ensure Scalive can run on Java 6
12-
scalacOptions += "-target:jvm-1.6"
13-
javacOptions ++= Seq("-source", "1.6", "-target", "1.6")
12+
// Ensure Scalive can run on Java from 8 (Scala 2.12 requires Java 8)
13+
scalacOptions += "-target:jvm-1.8"
14+
javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
1415

16+
// scala-compiler already embeds JLine, no need to add JLine dependency separately
1517
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided"
16-
libraryDependencies += "jline" % "jline" % "2.14.2"
1718

1819
// Add tools.jar to classpath
1920
// https://blogs.oracle.com/CoreJavaTechTips/entry/the_attach_api

dev/README.rst

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,12 @@ This is the directory that will be zipped when Scalive is released.
2424

2525
zip/
2626
scalive
27-
scalive.cmd
27+
scalive.bat
2828
scalive-<version>.jar <- ../../target/scala-2.11/scalive-<version>.jar
29-
jline-2.14.2.jar <- Download and put JLine JAR here
3029

31-
scala-library-2.10.6.jar
32-
scala-compiler-2.10..jar
33-
scala-reflect-2.10.6.jar
34-
35-
scala-library-2.11.8.jar
36-
scala-compiler-2.11.8.jar
37-
scala-reflect-2.11.8.jar
30+
scala-library-2.12.8.jar
31+
scala-compiler-2.12.8.jar
32+
scala-reflect-2.12.8.jar
3833

3934
While developing:
4035

@@ -46,22 +41,20 @@ Release
4641
-------
4742

4843
Based on the ``zip`` directory above, prepare a directory to be zipped and
49-
released (remember to remove uneccessary files, like .gitignore):
44+
released (remember to remove unneccessary files, like .gitignore):
5045

5146
::
5247

5348
scalive-<version>/
5449
scalive
55-
scalive.cmd
50+
scalive.bat
5651
scalive-<version>.jar <- Doesn't depend on Scala, thus doesn't follow Scala JAR naming
5752

58-
scala-library-2.10.4.jar
59-
scala-compiler-2.10.4.jar
60-
scala-reflect-2.10.4.jar
53+
scala-library-2.12.8.jar
54+
scala-compiler-2.12.8.jar
55+
scala-reflect-2.12.8.jar
6156

62-
scala-library-2.11.4.jar
63-
scala-compiler-2.11.4.jar
64-
scala-reflect-2.11.4.jar
57+
README.md
6558

6659
Then zip it:
6760

File renamed without changes.

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=0.13.12
1+
sbt.version=1.2.8

project/plugins.sbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
// Run sbt eclipse to create Eclipse project file
2-
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
1+
// Run "sbt eclipse" to create Eclipse project file
2+
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")

src/main/java/scalive/Classpath.java

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import java.net.URL;
88
import java.net.URLClassLoader;
99
import java.util.Arrays;
10+
import java.util.Objects;
11+
import java.util.stream.Collectors;
1012

1113
public class Classpath {
1214
private static final Method addURL = getAddURL();
@@ -51,7 +53,7 @@ public static String findJar(String[] jarSearchDirs, String jarPrefix) throws Il
5153
}
5254

5355
if (maxFile == null)
54-
throw new IllegalStateException("Could not find " + jarPrefix + " in " + join(jarSearchDirs, File.pathSeparator));
56+
throw new IllegalStateException("Could not find " + jarPrefix + " in " + String.join(File.pathSeparator, jarSearchDirs));
5557
else
5658
return maxFile.getPath();
5759
}
@@ -78,7 +80,7 @@ public static void findAndAddJar(
7880
* but only find and add the JAR to classpath if the representativeClass has not been loaded.
7981
*/
8082
public static void findAndAddJar(
81-
URLClassLoader cl, String representativeClass, String[] jarSearchDirs, String jarPrefix
83+
URLClassLoader cl, String representativeClass, String[] jarSearchDirs, String jarPrefix
8284
) throws IllegalAccessException, MalformedURLException, InvocationTargetException {
8385
try {
8486
Class.forName(representativeClass, true, cl);
@@ -90,7 +92,7 @@ public static void findAndAddJar(
9092
// http://stackoverflow.com/questions/4121567/embedded-scala-repl-inherits-parent-classpath
9193
public static String getClasspath(URLClassLoader cl) {
9294
URL[] urls = cl.getURLs();
93-
return join(urls, File.pathSeparator);
95+
return Arrays.stream(urls).map(Objects::toString).collect(Collectors.joining(File.pathSeparator));
9496
}
9597

9698
public static String getScalaVersion(
@@ -100,15 +102,4 @@ public static String getScalaVersion(
100102
Method m = k.getDeclaredMethod("versionNumberString");
101103
return (String) m.invoke(k);
102104
}
103-
104-
//--------------------------------------------------------------------------
105-
106-
private static String join(Object[] xs, String separator) {
107-
StringBuilder b = new StringBuilder();
108-
for (Object x: xs) {
109-
if (b.length() > 0) b.append(separator);
110-
b.append(x);
111-
}
112-
return b.toString();
113-
}
114105
}

src/main/java/scalive/Net.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,12 @@ public static void throwSocketTimeoutExceptionForLongInactivity(Socket socket) t
3636
* The sockets are closed in the order they are given.
3737
*/
3838
public static Runnable getSocketCleaner(final Socket... sockets) {
39-
return new Runnable() {
40-
@Override
41-
public void run() {
42-
for (Socket socket : sockets) {
43-
try {
44-
socket.close();
45-
} catch (IOException e) {
46-
// Ignore
47-
}
39+
return () -> {
40+
for (Socket socket : sockets) {
41+
try {
42+
socket.close();
43+
} catch (IOException e) {
44+
// Ignore
4845
}
4946
}
5047
};

src/main/java/scalive/client/Client.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package scalive.client;
22

3-
import jline.console.ConsoleReader;
3+
import scala.tools.jline_embedded.console.ConsoleReader;
44

55
import scalive.Log;
66
import scalive.Net;
@@ -23,7 +23,7 @@ public void run() {
2323
});
2424

2525
ConsoleReader reader = new ConsoleReader(System.in, System.out);
26-
Completer.setup(completerSocket, reader);
27-
Repl.run(replSocket, reader);
26+
ClientCompleter.setup(completerSocket, reader);
27+
ClientRepl.run(replSocket, reader);
2828
}
2929
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package scalive.client;
2+
3+
import scala.tools.jline_embedded.console.ConsoleReader;
4+
5+
import java.io.BufferedReader;
6+
import java.io.InputStream;
7+
import java.io.InputStreamReader;
8+
import java.io.OutputStream;
9+
import java.net.Socket;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.List;
12+
13+
/**
14+
* Input: cursor buffer
15+
*
16+
* Output: cursor candidate1 candidate2 candidate3...
17+
*/
18+
class ClientCompleter {
19+
static void setup(Socket socket, ConsoleReader reader) throws Exception {
20+
final InputStream in = socket.getInputStream();
21+
final OutputStream out = socket.getOutputStream();
22+
23+
final BufferedReader b = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
24+
25+
reader.addCompleter((String buffer, int cursor, List<CharSequence> candidates) -> {
26+
try {
27+
String request = String.format("%d %s\n", cursor, buffer);
28+
out.write(request.getBytes(StandardCharsets.UTF_8));
29+
out.flush();
30+
31+
String response = b.readLine();
32+
33+
// socket closed; the client should exit
34+
if (response == null) return -1;
35+
36+
String[] args = response.split(" ");
37+
38+
int c = Integer.parseInt(args[0]);
39+
40+
for (int i = 1; i < args.length; i++) {
41+
String candidate = args[i];
42+
candidates.add(candidate);
43+
}
44+
45+
return c;
46+
} catch (Exception e) {
47+
throw new RuntimeException(e);
48+
}
49+
});
50+
}
51+
}

0 commit comments

Comments
 (0)