]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.jshell/src/org/argeo/internal/cms/jshell/osgi/WrappingLoaderDelegate.java
Can specify JShell bundle
[lgpl/argeo-commons.git] / org.argeo.cms.jshell / src / org / argeo / internal / cms / jshell / osgi / WrappingLoaderDelegate.java
1 package org.argeo.internal.cms.jshell.osgi;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.net.MalformedURLException;
7 import java.net.URI;
8 import java.net.URISyntaxException;
9 import java.net.URL;
10 import java.net.URLConnection;
11 import java.net.URLStreamHandler;
12 import java.security.CodeSource;
13 import java.security.SecureClassLoader;
14 import java.time.Instant;
15 import java.time.ZoneId;
16 import java.time.ZonedDateTime;
17 import java.time.format.DateTimeFormatter;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Date;
21 import java.util.Enumeration;
22 import java.util.HashMap;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26
27 import jdk.jshell.execution.LoaderDelegate;
28 import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
29 import jdk.jshell.spi.ExecutionControl.ClassInstallException;
30 import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
31
32 /** A {@link LoaderDelegate} using a parent {@link ClassLoader}. */
33 class WrappingLoaderDelegate implements LoaderDelegate {
34 private final WrappingClassloader loader;
35 private final Map<String, Class<?>> klasses = new HashMap<>();
36
37 private static class WrappingClassloader extends SecureClassLoader {
38
39 private final Map<String, ClassFile> classFiles = new HashMap<>();
40
41 public WrappingClassloader(ClassLoader parent) {
42 super(parent);
43 }
44
45 private class ResourceURLStreamHandler extends URLStreamHandler {
46
47 private final String name;
48
49 ResourceURLStreamHandler(String name) {
50 this.name = name;
51 }
52
53 @Override
54 protected URLConnection openConnection(URL u) throws IOException {
55 return new URLConnection(u) {
56 private InputStream in;
57 private Map<String, List<String>> fields;
58 private List<String> fieldNames;
59
60 @Override
61 public void connect() {
62 if (connected) {
63 return;
64 }
65 connected = true;
66 ClassFile file = classFiles.get(name);
67 in = new ByteArrayInputStream(file.data);
68 fields = new LinkedHashMap<>();
69 fields.put("content-length", List.of(Integer.toString(file.data.length)));
70 Instant instant = new Date(file.timestamp).toInstant();
71 ZonedDateTime time = ZonedDateTime.ofInstant(instant, ZoneId.of("GMT"));
72 String timeStamp = DateTimeFormatter.RFC_1123_DATE_TIME.format(time);
73 fields.put("date", List.of(timeStamp));
74 fields.put("last-modified", List.of(timeStamp));
75 fieldNames = new ArrayList<>(fields.keySet());
76 }
77
78 @Override
79 public InputStream getInputStream() throws IOException {
80 connect();
81 return in;
82 }
83
84 @Override
85 public String getHeaderField(String name) {
86 connect();
87 return fields.getOrDefault(name, List.of()).stream().findFirst().orElse(null);
88 }
89
90 @Override
91 public Map<String, List<String>> getHeaderFields() {
92 connect();
93 return fields;
94 }
95
96 @Override
97 public String getHeaderFieldKey(int n) {
98 return n < fieldNames.size() ? fieldNames.get(n) : null;
99 }
100
101 @Override
102 public String getHeaderField(int n) {
103 String name = getHeaderFieldKey(n);
104
105 return name != null ? getHeaderField(name) : null;
106 }
107
108 };
109 }
110 }
111
112 void declare(String name, byte[] bytes) {
113 classFiles.put(toResourceString(name), new ClassFile(bytes, System.currentTimeMillis()));
114 }
115
116 @Override
117 protected Class<?> findClass(String name) throws ClassNotFoundException {
118 ClassFile file = classFiles.get(toResourceString(name));
119 if (file == null) {
120 return super.findClass(name);
121 }
122 return super.defineClass(name, file.data, 0, file.data.length, (CodeSource) null);
123 }
124
125 @Override
126 public URL findResource(String name) {
127 URL u = doFindResource(name);
128 return u != null ? u : super.findResource(name);
129 }
130
131 @Override
132 public Enumeration<URL> findResources(String name) throws IOException {
133 URL u = doFindResource(name);
134 Enumeration<URL> sup = super.findResources(name);
135
136 if (u == null) {
137 return sup;
138 }
139
140 List<URL> result = new ArrayList<>();
141
142 while (sup.hasMoreElements()) {
143 result.add(sup.nextElement());
144 }
145
146 result.add(u);
147
148 return Collections.enumeration(result);
149 }
150
151 private URL doFindResource(String name) {
152 if (classFiles.containsKey(name)) {
153 try {
154 return new URL(null, new URI("jshell", null, "/" + name, null).toString(),
155 new ResourceURLStreamHandler(name));
156 } catch (MalformedURLException | URISyntaxException ex) {
157 throw new InternalError(ex);
158 }
159 }
160
161 return null;
162 }
163
164 private String toResourceString(String className) {
165 return className.replace('.', '/') + ".class";
166 }
167
168 private static class ClassFile {
169 public final byte[] data;
170 public final long timestamp;
171
172 ClassFile(byte[] data, long timestamp) {
173 this.data = data;
174 this.timestamp = timestamp;
175 }
176
177 }
178 }
179
180 public WrappingLoaderDelegate(ClassLoader parentClassLoader) {
181 this.loader = new WrappingClassloader(parentClassLoader);
182
183 Thread.currentThread().setContextClassLoader(loader);
184 }
185
186 @Override
187 public void load(ClassBytecodes[] cbcs) throws ClassInstallException, EngineTerminationException {
188 boolean[] loaded = new boolean[cbcs.length];
189 try {
190 for (ClassBytecodes cbc : cbcs) {
191 loader.declare(cbc.name(), cbc.bytecodes());
192 }
193 for (int i = 0; i < cbcs.length; ++i) {
194 ClassBytecodes cbc = cbcs[i];
195 Class<?> klass = loader.loadClass(cbc.name());
196 klasses.put(cbc.name(), klass);
197 loaded[i] = true;
198 // Get class loaded to the point of, at least, preparation
199 klass.getDeclaredMethods();
200 }
201 } catch (Throwable ex) {
202 throw new ClassInstallException("load: " + ex.getMessage(), loaded);
203 }
204 }
205
206 @Override
207 public void classesRedefined(ClassBytecodes[] cbcs) {
208 for (ClassBytecodes cbc : cbcs) {
209 loader.declare(cbc.name(), cbc.bytecodes());
210 }
211 }
212
213 @Override
214 public void addToClasspath(String cp) {
215 // ignore
216 }
217
218 @Override
219 public Class<?> findClass(String name) throws ClassNotFoundException {
220 Class<?> klass = klasses.get(name);
221 if (klass == null) {
222 throw new ClassNotFoundException(name + " not found");
223 } else {
224 return klass;
225 }
226 }
227
228 }