Skip to content

Commit efbce4a

Browse files
committed
Clean implementation of the LoaderDelegate #22
1 parent afe931d commit efbce4a

File tree

2 files changed

+62
-240
lines changed

2 files changed

+62
-240
lines changed

RELEASE-NOTES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
- #11 Enable GitHub Actions
77
- #12 Dynamically change the kernel startup parameters
88
- #16 Upgrade gson transitive dependency
9-
- #21 Rendering stale var
9+
- #21 Rendering stale var
10+
- #22 Clean implementation of the LoaderDelegate
1011

1112
# 1.0-M1
1213

Lines changed: 60 additions & 239 deletions
Original file line numberDiff line numberDiff line change
@@ -1,278 +1,70 @@
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-
*/
241
package org.dflib.jjava.execution;
252

263
import jdk.jshell.execution.LoaderDelegate;
274
import jdk.jshell.spi.ExecutionControl;
285

29-
import java.io.ByteArrayInputStream;
306
import java.io.File;
31-
import java.io.IOException;
32-
import java.io.InputStream;
337
import java.net.MalformedURLException;
34-
import java.net.URI;
35-
import java.net.URISyntaxException;
368
import java.net.URL;
379
import java.net.URLClassLoader;
38-
import java.net.URLConnection;
39-
import java.net.URLStreamHandler;
10+
import java.nio.file.Path;
4011
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;
4912
import java.util.HashMap;
50-
import java.util.LinkedHashMap;
51-
import java.util.List;
5213
import java.util.Map;
5314

54-
/**
55-
* This code is a copy of jdk.jshell.execution.DefaultLoaderDelegate, with an option to unload stored classes
56-
*/
5715
public class JJavaLoaderDelegate implements LoaderDelegate {
5816

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;
21720

21821
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);
22126
}
22227

22328
@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);
23740
}
238-
} catch (Throwable ex) {
239-
throw new ExecutionControl.ClassInstallException("load: " + ex.getMessage(), loaded);
41+
installed[i++] = true;
24042
}
24143
}
24244

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-
25345
@Override
25446
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());
25749
}
25850
}
25951

26052
@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());
26660
}
267-
} catch (Exception ex) {
268-
throw new ExecutionControl.InternalException(ex.toString());
26961
}
27062
}
27163

27264
@Override
27365
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)) {
27668
// check if it was removed
27769
klass = loadClass(name);
27870
}
@@ -281,4 +73,33 @@ public Class<?> findClass(String name) throws ClassNotFoundException {
28173
}
28274
return klass;
28375
}
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+
}
284105
}

0 commit comments

Comments
 (0)