1
1
package scalive ;
2
2
3
+ import java .io .File ;
3
4
import java .io .InputStream ;
4
5
import java .io .OutputStream ;
5
6
import java .io .PrintStream ;
7
+ import java .lang .reflect .Field ;
6
8
import java .lang .reflect .Method ;
7
9
import java .net .Socket ;
10
+ import java .net .URL ;
8
11
import java .net .URLClassLoader ;
12
+ import java .util .Collection ;
13
+
14
+ import net .djpowell .liverepl .discovery .ClassLoaderInfo ;
15
+ import net .djpowell .liverepl .discovery .Discovery ;
9
16
10
17
public class Server {
18
+ // Load this Scala version if Scala has not been loaded in the target process
11
19
private static final String DEFAULT_SCALA_VERSION = "2.10.3" ;
12
20
13
- public static void serve (Socket client , String jarpath ) throws Exception {
21
+ // Use only one instance so that we have a stable list of class loaders
22
+ private static final Discovery DISCOVERY = new Discovery ();
23
+
24
+ public static void serve (Socket client , String jarpath , String clId ) throws Exception {
14
25
InputStream in = client .getInputStream ();
15
26
OutputStream out = client .getOutputStream ();
16
27
@@ -23,12 +34,14 @@ public static void serve(Socket client, String jarpath) throws Exception {
23
34
System .setErr (new PrintStream (out ));
24
35
25
36
try {
26
- URLClassLoader cl = ( URLClassLoader ) Server . class . getClassLoader ( );
27
- addJarsToClassLoader ( cl , jarpath ) ;
37
+ ClassLoader parentCl = getClassloader ( clId );
38
+ if ( parentCl == null ) return ;
28
39
29
- Class <?> repl = Class .forName ("scalive.Repl" );
30
- Method method = repl .getMethod ("run" , URLClassLoader .class , InputStream .class , OutputStream .class );
31
- method .invoke (null , cl , in , out );
40
+ URLClassLoader cl = createClassLoaderWithReplJars (jarpath , parentCl );
41
+ Class <?> repl = Class .forName ("scalive.Repl" , true , cl );
42
+ Method method = repl .getMethod ("run" , String .class , InputStream .class , OutputStream .class );
43
+ String classpath = getClasspathForRepl (cl , parentCl );
44
+ method .invoke (null , classpath , in , out );
32
45
} finally {
33
46
System .setIn (oldIn );
34
47
System .setOut (oldOut );
@@ -38,33 +51,125 @@ public static void serve(Socket client, String jarpath) throws Exception {
38
51
}
39
52
}
40
53
41
- private static void addJarsToClassLoader (URLClassLoader cl , String jarpath ) throws Exception {
54
+ //--------------------------------------------------------------------------
55
+
56
+ /**
57
+ * @param clId null means no class loader ID is specified; in this case,
58
+ * when there's only one class loader, it will be returned
59
+ *
60
+ * @return null if class loader not found or there are multiple class loaders
61
+ * but clId is not specified
62
+ */
63
+ private static ClassLoader getClassloader (String clId ) {
64
+ if (clId == null ) {
65
+ if (DISCOVERY .listClassLoaders ().size () > 1 ) {
66
+ DISCOVERY .dumpList (System .out );
67
+ return null ;
68
+ } else {
69
+ clId = "0" ;
70
+ }
71
+ }
72
+
73
+ ClassLoaderInfo cli = DISCOVERY .findClassLoader (clId );
74
+ if (cli == null ) {
75
+ System .out .println ("[Scalive] Could not find class loader: " + clId );
76
+ return null ;
77
+ }
78
+
79
+ return cli .getClassLoader ();
80
+ }
81
+
82
+ //--------------------------------------------------------------------------
83
+
84
+ private static URLClassLoader createClassLoaderWithReplJars (String jarpath , ClassLoader parentCl ) throws Exception {
85
+ URLClassLoader cl = new URLClassLoader (new URL [] {}, parentCl );
86
+
42
87
// Try scala-library first
43
- addJarToClassLoader (cl , jarpath + "/" + DEFAULT_SCALA_VERSION , "scala-library" , "scala.AnyVal" );
88
+ Classpath . addJarToURLClassLoader (cl , jarpath + "/" + DEFAULT_SCALA_VERSION , "scala-library" , "scala.AnyVal" );
44
89
45
90
// So that we can get the actual Scala version being used
46
- String version = getScalaVersion ();
91
+ String version = getScalaVersion (cl );
47
92
48
- addJarToClassLoader (cl , jarpath + "/" + version , "scala-compiler " , "scala.tools.nsc.interpreter.ILoop " );
49
- addJarToClassLoader (cl , jarpath + "/" + version , "scala-reflect " , "scala.reflect.runtime.JavaUniverse " );
93
+ Classpath . addJarToURLClassLoader (cl , jarpath + "/" + version , "scala-reflect " , "scala.reflect.runtime.JavaUniverse " );
94
+ Classpath . addJarToURLClassLoader (cl , jarpath + "/" + version , "scala-compiler " , "scala.tools.nsc.interpreter.ILoop " );
50
95
51
- addJarToClassLoader (cl , jarpath , "scalive-repl" , "scalive.Repl" );
52
- }
96
+ Classpath .addJarToURLClassLoader (cl , jarpath , "scalive-repl" , "scalive.Repl" );
53
97
54
- private static void addJarToClassLoader (
55
- URLClassLoader cl , String jarpath , String jarbase , String representativeClass
56
- ) throws Exception {
57
- try {
58
- Class .forName (representativeClass );
59
- } catch (ClassNotFoundException e ) {
60
- System .out .println ("[Scalive] Load " + jarbase );
61
- Classpath .findAndAddJar (cl , jarpath , jarbase );
62
- }
98
+ return cl ;
63
99
}
64
100
65
- private static String getScalaVersion () throws Exception {
66
- Class <?> k = Class .forName ("scala.util.Properties" );
101
+ private static String getScalaVersion (ClassLoader cl ) throws Exception {
102
+ Class <?> k = Class .forName ("scala.util.Properties" , true , cl );
67
103
Method m = k .getDeclaredMethod ("versionNumberString" );
68
104
return (String ) m .invoke (k );
69
105
}
106
+
107
+ //--------------------------------------------------------------------------
108
+
109
+ /** @return The classpath that should be set to the REPL
110
+ * @throws Exception */
111
+ private static String getClasspathForRepl (URLClassLoader withReplJars , ClassLoader parentCl ) throws Exception {
112
+ String fromParent = null ;
113
+ String fromRepl = getURLClasspath (withReplJars );
114
+
115
+ if (parentCl .getClass ().getName ().equals ("sbt.classpath.ClasspathFilter" )) {
116
+ fromParent = sbtGetClasspathFromClasspathFilter (withReplJars , parentCl );
117
+ } else if (parentCl instanceof URLClassLoader ) {
118
+ fromParent = getURLClasspath ((URLClassLoader ) parentCl );
119
+ }
120
+
121
+ return (fromParent == null ) ? fromRepl : fromParent + File .pathSeparator + fromRepl ;
122
+ }
123
+
124
+ // http://stackoverflow.com/questions/4121567/embedded-scala-repl-inherits-parent-classpath
125
+ private static String getURLClasspath (URLClassLoader cl ) {
126
+ URL [] urls = cl .getURLs ();
127
+ StringBuffer buf = new StringBuffer ();
128
+ for (URL url : urls ) {
129
+ if (buf .length () > 0 ) buf .append (File .pathSeparator );
130
+ buf .append (url );
131
+ }
132
+ return buf .toString ();
133
+ }
134
+
135
+ //--------------------------------------------------------------------------
136
+
137
+ // http://www.scala-sbt.org/release/api/index.html#sbt.classpath.ClasspathFilter
138
+ private static String sbtGetClasspathFromClasspathFilter (URLClassLoader withReplJars , ClassLoader parentCl ) throws Exception {
139
+ ClassLoader mainClassLoader = sbtFindMainClassLoader ();
140
+ Class <?> ClasspathFilterClass = Class .forName ("sbt.classpath.ClasspathFilter" , true , mainClassLoader );
141
+ Field classpathField = ClasspathFilterClass .getDeclaredField ("classpath" );
142
+ classpathField .setAccessible (true );
143
+
144
+ Object set = classpathField .get (parentCl );
145
+ Class <?> setClass = Class .forName ("scala.collection.TraversableOnce" , true , withReplJars );
146
+ Method mkString = setClass .getDeclaredMethod ("mkString" , String .class );
147
+ return (String ) mkString .invoke (set , File .pathSeparator );
148
+ }
149
+
150
+ private static ClassLoader sbtFindMainClassLoader () throws Exception {
151
+ // System Class Loader:
152
+ //
153
+ // #Id ClassLoader Info
154
+ // 0 AppClassLoader <system>
155
+ //
156
+ // Thread Context Class Loaders:
157
+ //
158
+ // #Id ClassLoader Info
159
+ // 1 URLClassLoader main #1 [main] <--- Take out this one
160
+ // 2 ClasspathFilter xitrum-akka.actor.default-dispatcher-2 #31 [run-main-group-0]
161
+
162
+ Collection <ClassLoaderInfo > clis = DISCOVERY .listClassLoaders ();
163
+ for (ClassLoaderInfo cli : clis ) {
164
+ ClassLoader cl = cli .getClassLoader ();
165
+ String name = cli .getClassLoaderName ();
166
+
167
+ // Can't use info because it can be "process reaper #8 [system]" etc.
168
+ boolean mainClassLoader = cl instanceof URLClassLoader && "URLClassLoader" .equals (name );
169
+
170
+ if (mainClassLoader ) return cl ;
171
+ }
172
+
173
+ throw new Exception ("[Scalive] SBT main class loader not found" );
174
+ }
70
175
}
0 commit comments