1
- /*
2
- * The MIT License (MIT)
3
- *
4
- * Copyright (c) 2018 Spencer Park
5
- *
6
- * Permission is hereby granted, free of charge, to any person obtaining a copy
7
- * of this software and associated documentation files (the "Software"), to deal
8
- * in the Software without restriction, including without limitation the rights
9
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- * copies of the Software, and to permit persons to whom the Software is
11
- * furnished to do so, subject to the following conditions:
12
- *
13
- * The above copyright notice and this permission notice shall be included in all
14
- * copies or substantial portions of the Software.
15
- *
16
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- * SOFTWARE.
23
- */
24
1
package org .dflib .jjava .execution ;
25
2
26
3
import jdk .jshell .execution .LoaderDelegate ;
27
4
import jdk .jshell .spi .ExecutionControl ;
28
5
29
- import java .io .ByteArrayInputStream ;
30
6
import java .io .File ;
31
- import java .io .IOException ;
32
- import java .io .InputStream ;
33
7
import java .net .MalformedURLException ;
34
- import java .net .URI ;
35
- import java .net .URISyntaxException ;
36
8
import java .net .URL ;
37
9
import java .net .URLClassLoader ;
38
- import java .net .URLConnection ;
39
- import java .net .URLStreamHandler ;
10
+ import java .nio .file .Path ;
40
11
import java .security .CodeSource ;
41
- import java .time .Instant ;
42
- import java .time .ZoneId ;
43
- import java .time .ZonedDateTime ;
44
- import java .time .format .DateTimeFormatter ;
45
- import java .util .ArrayList ;
46
- import java .util .Collections ;
47
- import java .util .Date ;
48
- import java .util .Enumeration ;
49
12
import java .util .HashMap ;
50
- import java .util .LinkedHashMap ;
51
- import java .util .List ;
52
13
import java .util .Map ;
53
14
54
- /**
55
- * This code is a copy of jdk.jshell.execution.DefaultLoaderDelegate, with an option to unload stored classes
56
- */
57
15
public class JJavaLoaderDelegate implements LoaderDelegate {
58
16
59
- private final RemoteClassLoader loader ;
60
- private final Map <String , Class <?>> klasses = new HashMap <>();
61
-
62
- private static class RemoteClassLoader extends URLClassLoader {
63
-
64
- private final Map <String , RemoteClassLoader .ClassFile > classFiles = new HashMap <>();
65
-
66
- RemoteClassLoader () {
67
- super (new URL [0 ]);
68
- }
69
-
70
- private class ResourceURLStreamHandler extends URLStreamHandler {
71
-
72
- private final String name ;
73
-
74
- ResourceURLStreamHandler (String name ) {
75
- this .name = name ;
76
- }
77
-
78
- @ Override
79
- protected URLConnection openConnection (URL u ) throws IOException {
80
- return new URLConnection (u ) {
81
- private InputStream in ;
82
- private Map <String , List <String >> fields ;
83
- private List <String > fieldNames ;
84
-
85
- @ Override
86
- public void connect () {
87
- if (connected ) {
88
- return ;
89
- }
90
- connected = true ;
91
- RemoteClassLoader .ClassFile file = classFiles .get (name );
92
- in = new ByteArrayInputStream (file .data );
93
- fields = new LinkedHashMap <>();
94
- fields .put ("content-length" , List .of (Integer .toString (file .data .length )));
95
- Instant instant = new Date (file .timestamp ).toInstant ();
96
- ZonedDateTime time = ZonedDateTime .ofInstant (instant , ZoneId .of ("GMT" ));
97
- String timeStamp = DateTimeFormatter .RFC_1123_DATE_TIME .format (time );
98
- fields .put ("date" , List .of (timeStamp ));
99
- fields .put ("last-modified" , List .of (timeStamp ));
100
- fieldNames = new ArrayList <>(fields .keySet ());
101
- }
102
-
103
- @ Override
104
- public InputStream getInputStream () throws IOException {
105
- connect ();
106
- return in ;
107
- }
108
-
109
- @ Override
110
- public String getHeaderField (String name ) {
111
- connect ();
112
- return fields .getOrDefault (name , List .of ())
113
- .stream ()
114
- .findFirst ()
115
- .orElse (null );
116
- }
117
-
118
- @ Override
119
- public Map <String , List <String >> getHeaderFields () {
120
- connect ();
121
- return fields ;
122
- }
123
-
124
- @ Override
125
- public String getHeaderFieldKey (int n ) {
126
- return n < fieldNames .size () ? fieldNames .get (n ) : null ;
127
- }
128
-
129
- @ Override
130
- public String getHeaderField (int n ) {
131
- String name = getHeaderFieldKey (n );
132
-
133
- return name != null ? getHeaderField (name ) : null ;
134
- }
135
-
136
- };
137
- }
138
- }
139
-
140
- void declare (String name , byte [] bytes ) {
141
- classFiles .put (toResourceString (name ), new RemoteClassLoader .ClassFile (bytes , System .currentTimeMillis ()));
142
- }
143
-
144
- boolean isDeclared (String name ) {
145
- return classFiles .containsKey (name );
146
- }
147
-
148
- @ Override
149
- protected Class <?> findClass (String name ) throws ClassNotFoundException {
150
- RemoteClassLoader .ClassFile file = classFiles .get (toResourceString (name ));
151
- if (file == null ) {
152
- return super .findClass (name );
153
- }
154
- return super .defineClass (name , file .data , 0 , file .data .length , (CodeSource ) null );
155
- }
156
-
157
- @ Override
158
- public URL findResource (String name ) {
159
- URL u = doFindResource (name );
160
- return u != null ? u : super .findResource (name );
161
- }
162
-
163
- @ Override
164
- public Enumeration <URL > findResources (String name ) throws IOException {
165
- URL u = doFindResource (name );
166
- Enumeration <URL > sup = super .findResources (name );
167
-
168
- if (u == null ) {
169
- return sup ;
170
- }
171
-
172
- List <URL > result = new ArrayList <>();
173
-
174
- while (sup .hasMoreElements ()) {
175
- result .add (sup .nextElement ());
176
- }
177
-
178
- result .add (u );
179
-
180
- return Collections .enumeration (result );
181
- }
182
-
183
- private URL doFindResource (String name ) {
184
- if (classFiles .containsKey (name )) {
185
- try {
186
- return new URL (null ,
187
- new URI ("jshell" , null , "/" + name , null ).toString (),
188
- new RemoteClassLoader .ResourceURLStreamHandler (name ));
189
- } catch (MalformedURLException | URISyntaxException ex ) {
190
- throw new InternalError (ex );
191
- }
192
- }
193
-
194
- return null ;
195
- }
196
-
197
- private String toResourceString (String className ) {
198
- return className .replace ('.' , '/' ) + ".class" ;
199
- }
200
-
201
- @ Override
202
- public void addURL (URL url ) {
203
- super .addURL (url );
204
- }
205
-
206
- private static class ClassFile {
207
- public final byte [] data ;
208
- public final long timestamp ;
209
-
210
- ClassFile (byte [] data , long timestamp ) {
211
- this .data = data ;
212
- this .timestamp = timestamp ;
213
- }
214
-
215
- }
216
- }
17
+ private final Map <String , byte []> declaredClasses ;
18
+ private final Map <String , Class <?>> loadedClasses ;
19
+ private final BytecodeClassLoader classLoader ;
217
20
218
21
public JJavaLoaderDelegate () {
219
- this .loader = new RemoteClassLoader ();
220
- Thread .currentThread ().setContextClassLoader (loader );
22
+ this .declaredClasses = new HashMap <>();
23
+ this .loadedClasses = new HashMap <>();
24
+ this .classLoader = new BytecodeClassLoader ();
25
+ Thread .currentThread ().setContextClassLoader (classLoader );
221
26
}
222
27
223
28
@ Override
224
- public void load (ExecutionControl .ClassBytecodes [] cbcs )
225
- throws ExecutionControl .ClassInstallException {
226
- boolean [] loaded = new boolean [cbcs .length ];
227
- try {
228
- for (ExecutionControl .ClassBytecodes cbc : cbcs ) {
229
- loader .declare (cbc .name (), cbc .bytecodes ());
230
- }
231
- for (int i = 0 ; i < cbcs .length ; ++i ) {
232
- ExecutionControl .ClassBytecodes cbc = cbcs [i ];
233
- Class <?> klass = loadClass (cbc .name ());
234
- loaded [i ] = true ;
235
- // Get class loaded to the point of, at least, preparation
236
- klass .getDeclaredMethods ();
29
+ public void load (ExecutionControl .ClassBytecodes [] cbcs ) throws ExecutionControl .ClassInstallException {
30
+ boolean [] installed = new boolean [cbcs .length ];
31
+ int i = 0 ;
32
+ for (var cbc : cbcs ) {
33
+ declaredClasses .put (cbc .name (), cbc .bytecodes ());
34
+ try {
35
+ Class <?> loaderClass = classLoader .findClass (cbc .name ());
36
+ loadedClasses .put (cbc .name (), loaderClass );
37
+ } catch (ClassNotFoundException e ) {
38
+ throw new ExecutionControl .ClassInstallException ("Unable to load class " + cbc .name ()
39
+ + ": " + e .getMessage (), installed );
237
40
}
238
- } catch (Throwable ex ) {
239
- throw new ExecutionControl .ClassInstallException ("load: " + ex .getMessage (), loaded );
41
+ installed [i ++] = true ;
240
42
}
241
43
}
242
44
243
- private Class <?> loadClass (String name ) throws ClassNotFoundException {
244
- Class <?> klass = loader .loadClass (name );
245
- klasses .put (name , klass );
246
- return klass ;
247
- }
248
-
249
- void unloadClass (String name ) throws ClassNotFoundException {
250
- klasses .remove (name );
251
- }
252
-
253
45
@ Override
254
46
public void classesRedefined (ExecutionControl .ClassBytecodes [] cbcs ) {
255
- for ( ExecutionControl . ClassBytecodes cbc : cbcs ) {
256
- loader . declare (cbc .name (), cbc .bytecodes ());
47
+ for ( var cbc : cbcs ) {
48
+ declaredClasses . put (cbc .name (), cbc .bytecodes ());
257
49
}
258
50
}
259
51
260
52
@ Override
261
- public void addToClasspath (String cp )
262
- throws ExecutionControl .InternalException {
263
- try {
264
- for (String path : cp .split (File .pathSeparator )) {
265
- loader .addURL (new File (path ).toURI ().toURL ());
53
+ public void addToClasspath (String path ) throws ExecutionControl .InternalException {
54
+ for (String next : path .split (File .pathSeparator )) {
55
+ try {
56
+ classLoader .addURL (Path .of (next ).toUri ().toURL ());
57
+ } catch (MalformedURLException e ) {
58
+ throw new ExecutionControl .InternalException ("Unable to resolve classpath " + next
59
+ + ": " + e .getMessage ());
266
60
}
267
- } catch (Exception ex ) {
268
- throw new ExecutionControl .InternalException (ex .toString ());
269
61
}
270
62
}
271
63
272
64
@ Override
273
65
public Class <?> findClass (String name ) throws ClassNotFoundException {
274
- Class <?> klass = klasses .get (name );
275
- if (klass == null && loader . isDeclared (name )) {
66
+ Class <?> klass = loadedClasses .get (name );
67
+ if (klass == null && declaredClasses . containsKey (name )) {
276
68
// check if it was removed
277
69
klass = loadClass (name );
278
70
}
@@ -281,4 +73,33 @@ public Class<?> findClass(String name) throws ClassNotFoundException {
281
73
}
282
74
return klass ;
283
75
}
76
+
77
+ private Class <?> loadClass (String name ) throws ClassNotFoundException {
78
+ return classLoader .loadClass (name );
79
+ }
80
+
81
+ public void unloadClass (String name ) {
82
+ loadedClasses .remove (name );
83
+ declaredClasses .remove (name );
84
+ }
85
+
86
+ class BytecodeClassLoader extends URLClassLoader {
87
+
88
+ public BytecodeClassLoader () {
89
+ super (new URL [0 ]);
90
+ }
91
+
92
+ public void addURL (URL url ) {
93
+ super .addURL (url );
94
+ }
95
+
96
+ @ Override
97
+ protected Class <?> findClass (String name ) throws ClassNotFoundException {
98
+ byte [] data = declaredClasses .get (name );
99
+ if (data == null ) {
100
+ return super .findClass (name );
101
+ }
102
+ return super .defineClass (name , data , 0 , data .length , (CodeSource ) null );
103
+ }
104
+ }
284
105
}
0 commit comments